From 9dd7f7befd8d1e43f4083cce3b111cc317aeb9ec Mon Sep 17 00:00:00 2001 From: Trezy Date: Wed, 13 May 2026 13:39:41 -0500 Subject: [PATCH] docs: add multi-language code examples Signed-off-by: Trezy --- .../docs/api-reference/admin/api-clients.md | 225 ++++++- .../docs/api-reference/admin/api-keys.md | 147 ++++- .../docs/api-reference/admin/backfill.md | 109 +++- .../docs/api-reference/admin/domains.md | 158 ++++- .../docs/api-reference/admin/events.md | 62 +- .../docs/api-reference/admin/labelers.md | 163 +++++- .../docs/api-reference/admin/lexicons.md | 367 +++++++++++- .../docs/api-reference/admin/plugins.md | 234 +++++++- .../api-reference/admin/script-variables.md | 53 +- .../docs/api-reference/admin/settings.md | 80 ++- .../content/docs/api-reference/admin/stats.md | 54 +- .../content/docs/api-reference/admin/users.md | 273 ++++++++- .../docs/api-reference/admin/xrpc-proxy.md | 97 ++- .../docs/api-reference/oauth/api-clients.md | 178 +++++- .../content/docs/api-reference/xrpc-api.md | 416 ++++++++++++- .../docs/experimental/spaces/credentials.md | 158 ++++- .../content/docs/experimental/spaces/index.md | 38 +- .../docs/experimental/spaces/invites.md | 251 +++++++- .../experimental/spaces/managing-spaces.md | 302 +++++++++- .../docs/experimental/spaces/members.md | 254 +++++++- .../docs/experimental/spaces/records.md | 551 +++++++++++++++++- .../docs/getting-started/authentication.md | 184 +++++- .../docs/content/docs/guides/api-clients.md | 319 +++++++++- packages/docs/content/docs/guides/api-keys.md | 48 +- .../docs/content/docs/guides/event-logs.md | 149 ++++- packages/docs/content/docs/guides/labelers.md | 139 ++++- .../reference/script-examples/batch-save.md | 63 +- .../script-examples/cascading-delete.md | 49 +- .../script-examples/complex-mutations.md | 59 +- .../script-examples/create-record.md | 52 +- .../script-examples/sidecar-records.md | 55 +- .../script-examples/signed-record-verify.md | 28 +- .../script-examples/signed-record.md | 43 +- .../script-examples/update-or-delete.md | 156 ++++- .../script-examples/upsert-record.md | 100 +++- .../content/docs/sdk/lex-agent/changelog.md | 2 +- .../sdk/oauth-client-browser/changelog.md | 2 +- .../docs/sdk/oauth-client-node/changelog.md | 2 +- .../docs/sdk/oauth-client/changelog.md | 2 +- .../content/docs/tutorials/statusphere.md | 251 +++++++- packages/docs/source.config.ts | 4 +- 41 files changed, 5748 insertions(+), 129 deletions(-) diff --git a/packages/docs/content/docs/api-reference/admin/api-clients.md b/packages/docs/content/docs/api-reference/admin/api-clients.md index bbc3ca7..ff9ce44 100644 --- a/packages/docs/content/docs/api-reference/admin/api-clients.md +++ b/packages/docs/content/docs/api-reference/admin/api-clients.md @@ -12,7 +12,21 @@ Each client has an `hvc_`-prefixed client key and an `hvs_`-prefixed client secr Third-party apps can also create, list, and delete their own API clients programmatically via the [XRPC API](../oauth/api-clients.md), without needing admin access. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -25,7 +39,49 @@ GET /admin/api-clients Requires `api-clients:view`. Returns clients ordered by `created_at` descending. Secrets are never returned. -```sh +```ts tab="TypeScript" tab-group="language" +interface ApiClient { + id: string; + client_key: string; + name: string; + client_id_url: string; + client_uri: string; + redirect_uris: string[]; + scopes: string; + rate_limit_capacity: number; + rate_limit_refill_rate: number; + is_active: boolean; + created_by: string; + created_at: string; + updated_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/api-clients", { + headers, +}); +const data: ApiClient[] = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/api-clients", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let response = client + .get("http://127.0.0.1:3000/admin/api-clients") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/api-clients", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/api-clients -H "$AUTH" ``` @@ -61,7 +117,85 @@ POST /admin/api-clients Requires `api-clients:create`. Generates a `client_key` and `client_secret`. Store the secret — it won't be shown again. -```sh +```ts tab="TypeScript" tab-group="language" +interface ApiClient { + id: string; + client_key: string; + client_secret: string; + name: string; + client_id_url: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/api-clients", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "My Game Client", + client_id_url: "https://example.com/client-metadata.json", + client_uri: "https://example.com", + redirect_uris: ["https://example.com/callback"], + scopes: "atproto", + rate_limit_capacity: 200, + rate_limit_refill_rate: 5.0, + }), +}); +const data: ApiClient = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/api-clients", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "My Game Client", + client_id_url: "https://example.com/client-metadata.json", + client_uri: "https://example.com", + redirect_uris: ["https://example.com/callback"], + scopes: "atproto", + rate_limit_capacity: 200, + rate_limit_refill_rate: 5.0, + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/api-clients") + .bearer_auth(token) + .json(&serde_json::json!({ + "name": "My Game Client", + "client_id_url": "https://example.com/client-metadata.json", + "client_uri": "https://example.com", + "redirect_uris": ["https://example.com/callback"], + "scopes": "atproto", + "rate_limit_capacity": 200, + "rate_limit_refill_rate": 5.0 + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "name": "My Game Client", + "client_id_url": "https://example.com/client-metadata.json", + "client_uri": "https://example.com", + "redirect_uris": ["https://example.com/callback"], + "scopes": "atproto", + "rate_limit_capacity": 200, + "rate_limit_refill_rate": 5.0 +}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/api-clients", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/api-clients \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -116,6 +250,63 @@ PUT /admin/api-clients/{id} Requires `api-clients:edit`. All fields are optional — only provided fields are changed. Updating either rate-limit field re-registers the client with the rate limiter using the new values. +```ts tab="TypeScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/api-clients/01J9...", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "Renamed Client", + rate_limit_capacity: 500, + }), +}); +``` +```js tab="JavaScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/api-clients/01J9...", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "Renamed Client", + rate_limit_capacity: 500, + }), +}); +``` +```rust tab="Rust" tab-group="language" +client + .put("http://127.0.0.1:3000/admin/api-clients/01J9...") + .bearer_auth(token) + .json(&serde_json::json!({ + "name": "Renamed Client", + "rate_limit_capacity": 500 + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "name": "Renamed Client", + "rate_limit_capacity": 500 +}`) +req, _ := http.NewRequest("PUT", "http://127.0.0.1:3000/admin/api-clients/01J9...", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" +curl -X PUT http://127.0.0.1:3000/admin/api-clients/01J9... \ + -H "$AUTH" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Renamed Client", + "rate_limit_capacity": 500 + }' +``` + | Field | Type | Description | | ------------------------ | -------- | ---------------------------------------------------------------------- | | `name` | string | New display name | @@ -138,4 +329,32 @@ DELETE /admin/api-clients/{id} Requires `api-clients:delete`. Removes the client from the OAuth registry, the rate limiter, and the client identity store. +```ts tab="TypeScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/api-clients/01J9...", { + method: "DELETE", + headers, +}); +``` +```js tab="JavaScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/api-clients/01J9...", { + method: "DELETE", + headers, +}); +``` +```rust tab="Rust" tab-group="language" +client + .delete("http://127.0.0.1:3000/admin/api-clients/01J9...") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/api-clients/01J9...", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" +curl -X DELETE http://127.0.0.1:3000/admin/api-clients/01J9... -H "$AUTH" +``` + **Response**: `204 No Content` diff --git a/packages/docs/content/docs/api-reference/admin/api-keys.md b/packages/docs/content/docs/api-reference/admin/api-keys.md index d1044b7..87e038c 100644 --- a/packages/docs/content/docs/api-reference/admin/api-keys.md +++ b/packages/docs/content/docs/api-reference/admin/api-keys.md @@ -4,7 +4,25 @@ title: "API Keys" Manage API keys for programmatic access. See the [API Keys guide](../../guides/api-keys.md) for usage details. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` + +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` + +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` + +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` + +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -17,7 +35,70 @@ POST /admin/api-keys Requires `api-keys:create` permission. -```sh +```ts tab="TypeScript" tab-group="language" +interface ApiKey { + id: string; + name: string; + key: string; + key_prefix: string; + permissions: string[]; +} + +const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "CI Deploy", + permissions: ["lexicons:read", "lexicons:create", "backfill:create"], + }), +}); +const data: ApiKey = await response.json(); +``` + +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "CI Deploy", + permissions: ["lexicons:read", "lexicons:create", "backfill:create"], + }), +}); +const data = await response.json(); +``` + +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let response = client + .post("http://127.0.0.1:3000/admin/api-keys") + .bearer_auth(token) + .json(&serde_json::json!({ + "name": "CI Deploy", + "permissions": ["lexicons:read", "lexicons:create", "backfill:create"] + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` + +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "name": "CI Deploy", + "permissions": ["lexicons:read", "lexicons:create", "backfill:create"] +}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/api-keys", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` + +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/api-keys \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -54,7 +135,36 @@ GET /admin/api-keys Requires `api-keys:read` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { + headers, +}); +const data: ApiKey[] = await response.json(); +``` + +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { + headers, +}); +const data = await response.json(); +``` + +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/api-keys") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` + +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/api-keys", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` + +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/api-keys -H "$AUTH" ``` @@ -84,7 +194,36 @@ DELETE /admin/api-keys/{id} Requires `api-keys:delete` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000", + { method: "DELETE", headers }, +); +``` + +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000", + { method: "DELETE", headers }, +); +``` + +```rust tab="Rust" tab-group="language" +client + .delete("http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000") + .bearer_auth(token) + .send() + .await?; +``` + +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", + "http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` + +```sh tab="cURL" tab-group="language" curl -X DELETE http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000 \ -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/backfill.md b/packages/docs/content/docs/api-reference/admin/backfill.md index 771101d..f667856 100644 --- a/packages/docs/content/docs/api-reference/admin/backfill.md +++ b/packages/docs/content/docs/api-reference/admin/backfill.md @@ -4,7 +4,21 @@ title: "Backfill" Create and monitor historical backfill jobs. See the [Backfill guide](../../guides/backfill.md) for background. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -15,7 +29,57 @@ AUTH="Authorization: Bearer $TOKEN" POST /admin/backfill ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface BackfillJob { + id: string; + status: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/backfill", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + collection: "xyz.statusphere.status", + }), +}); +const data: BackfillJob = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/backfill", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + collection: "xyz.statusphere.status", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let response = client + .post("http://127.0.0.1:3000/admin/backfill") + .bearer_auth(token) + .json(&serde_json::json!({ + "collection": "xyz.statusphere.status" + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"collection": "xyz.statusphere.status"}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/backfill", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/backfill \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -42,7 +106,46 @@ curl -X POST http://127.0.0.1:3000/admin/backfill \ GET /admin/backfill/status ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface BackfillJob { + id: string; + collection: string | null; + did: string | null; + status: string; + total_repos: number; + processed_repos: number; + total_records: number; + error: string | null; + started_at: string | null; + completed_at: string | null; + created_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/backfill/status", { + headers, +}); +const data: BackfillJob[] = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/backfill/status", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/backfill/status") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/backfill/status", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/backfill/status -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/domains.md b/packages/docs/content/docs/api-reference/admin/domains.md index 3285140..cb278d3 100644 --- a/packages/docs/content/docs/api-reference/admin/domains.md +++ b/packages/docs/content/docs/api-reference/admin/domains.md @@ -4,7 +4,21 @@ title: "Domains" Manage the domains a HappyView instance serves. Each domain gets its own atproto OAuth client identity. The primary domain is set from `PUBLIC_URL` on first boot. All endpoints require the `settings:manage` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -15,7 +29,41 @@ AUTH="Authorization: Bearer $TOKEN" GET /admin/domains ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface Domain { + id: string; + url: string; + is_primary: boolean; + created_at: string; + updated_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/domains", { + headers, +}); +const data: Domain[] = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/domains", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let response = client + .get("http://127.0.0.1:3000/admin/domains") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/domains", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/domains -H "$AUTH" ``` @@ -39,7 +87,59 @@ curl http://127.0.0.1:3000/admin/domains -H "$AUTH" POST /admin/domains ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface Domain { + id: string; + url: string; + is_primary: boolean; + created_at: string; + updated_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/domains", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: "https://api.example.com", + }), +}); +const data: Domain = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/domains", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: "https://api.example.com", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/domains") + .bearer_auth(token) + .json(&serde_json::json!({ + "url": "https://api.example.com" + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"url": "https://api.example.com"}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/domains", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/domains \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -72,7 +172,31 @@ Also builds an OAuth client for the domain and updates the in-memory cache. DELETE /admin/domains/{id} ``` -```sh +```ts tab="TypeScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001", { + method: "DELETE", + headers, +}); +``` +```js tab="JavaScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001", { + method: "DELETE", + headers, +}); +``` +```rust tab="Rust" tab-group="language" +client + .delete("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X DELETE http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001 \ -H "$AUTH" ``` @@ -89,7 +213,31 @@ Also removes the domain's OAuth client and cache entry. POST /admin/domains/{id}/primary ``` -```sh +```ts tab="TypeScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary", { + method: "POST", + headers, +}); +``` +```js tab="JavaScript" tab-group="language" +await fetch("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary", { + method: "POST", + headers, +}); +``` +```rust tab="Rust" tab-group="language" +client + .post("http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/domains/550e8400-e29b-41d4-a716-446655440001/primary \ -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/events.md b/packages/docs/content/docs/api-reference/admin/events.md index 86ac4f1..0d2758a 100644 --- a/packages/docs/content/docs/api-reference/admin/events.md +++ b/packages/docs/content/docs/api-reference/admin/events.md @@ -4,7 +4,21 @@ title: "Event Logs" HappyView logs system events — lexicon changes, record operations, script errors, user actions, and more. See the [Event Logs guide](../../guides/event-logs.md) for details on event types and retention. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -15,7 +29,51 @@ AUTH="Authorization: Bearer $TOKEN" GET /admin/events ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface EventLog { + id: string; + event_type: string; + severity: string; + actor_did: string; + subject: string; + detail: Record; + created_at: string; +} + +interface EventLogResponse { + events: EventLog[]; + cursor: string | null; +} + +const response = await fetch( + "http://127.0.0.1:3000/admin/events?severity=error&limit=10", + { headers }, +); +const data: EventLogResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/events?severity=error&limit=10", + { headers }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let response = client + .get("http://127.0.0.1:3000/admin/events") + .query(&[("severity", "error"), ("limit", "10")]) + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/events?severity=error&limit=10", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl "http://127.0.0.1:3000/admin/events?severity=error&limit=10" -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/labelers.md b/packages/docs/content/docs/api-reference/admin/labelers.md index 5f95f0c..8f3fb55 100644 --- a/packages/docs/content/docs/api-reference/admin/labelers.md +++ b/packages/docs/content/docs/api-reference/admin/labelers.md @@ -4,7 +4,21 @@ title: "Labelers" Manage external labeler subscriptions. See the [Labelers guide](../../guides/labelers.md) for background. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -17,7 +31,42 @@ POST /admin/labelers Requires `labelers:create` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/labelers", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ did: "did:plc:ar7c4by46qjdydhdevvrndac" }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/labelers", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ did: "did:plc:ar7c4by46qjdydhdevvrndac" }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/labelers") + .bearer_auth(token) + .json(&serde_json::json!({ "did": "did:plc:ar7c4by46qjdydhdevvrndac" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ "did": "did:plc:ar7c4by46qjdydhdevvrndac" }`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/labelers", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/labelers \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -38,7 +87,40 @@ GET /admin/labelers Requires `labelers:read` permission. -```sh +```ts tab="TypeScript" tab-group="language" +interface Labeler { + did: string; + status: string; + cursor: number | null; + created_at: string; + updated_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/labelers", { + headers, +}); +const data: Labeler[] = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/labelers", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/labelers") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/labelers", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/labelers -H "$AUTH" ``` @@ -72,7 +154,48 @@ PATCH /admin/labelers/{did} Requires `labelers:create` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "PATCH", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "paused" }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "PATCH", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "paused" }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .patch("http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac") + .bearer_auth(token) + .json(&serde_json::json!({ "status": "paused" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ "status": "paused" }`) +req, _ := http.NewRequest("PATCH", "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X PATCH http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -93,7 +216,37 @@ DELETE /admin/labelers/{did} Requires `labelers:delete` permission. Removes the subscription and all labels emitted by this labeler. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "DELETE", + headers, + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "DELETE", + headers, + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .delete("http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X DELETE http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/lexicons.md b/packages/docs/content/docs/api-reference/admin/lexicons.md index f56b59a..99c92fa 100644 --- a/packages/docs/content/docs/api-reference/admin/lexicons.md +++ b/packages/docs/content/docs/api-reference/admin/lexicons.md @@ -4,7 +4,21 @@ title: "Lexicons" Manage lexicons and network lexicons. See the [Lexicons guide](../../guides/lexicons.md) for background on how lexicons drive indexing and XRPC routing. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -15,7 +29,134 @@ AUTH="Authorization: Bearer $TOKEN" POST /admin/lexicons ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface LexiconResult { + id: string; + revision: number; +} + +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + lexicon_json: { + lexicon: 1, + id: "xyz.statusphere.status", + defs: { + main: { + type: "record", + key: "tid", + record: { + type: "object", + required: ["status", "createdAt"], + properties: { + status: { type: "string", maxGraphemes: 1 }, + createdAt: { type: "string", format: "datetime" }, + }, + }, + }, + }, + }, + backfill: true, + target_collection: null, + }), +}); +const data: LexiconResult = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + lexicon_json: { + lexicon: 1, + id: "xyz.statusphere.status", + defs: { + main: { + type: "record", + key: "tid", + record: { + type: "object", + required: ["status", "createdAt"], + properties: { + status: { type: "string", maxGraphemes: 1 }, + createdAt: { type: "string", format: "datetime" }, + }, + }, + }, + }, + }, + backfill: true, + target_collection: null, + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/lexicons") + .bearer_auth(token) + .json(&serde_json::json!({ + "lexicon_json": { + "lexicon": 1, + "id": "xyz.statusphere.status", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": ["status", "createdAt"], + "properties": { + "status": { "type": "string", "maxGraphemes": 1 }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + } + } + }, + "backfill": true, + "target_collection": null + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "lexicon_json": { + "lexicon": 1, + "id": "xyz.statusphere.status", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": ["status", "createdAt"], + "properties": { + "status": { "type": "string", "maxGraphemes": 1 }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + } + } + }, + "backfill": true, + "target_collection": null +}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/lexicons", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/lexicons \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -49,7 +190,41 @@ curl -X POST http://127.0.0.1:3000/admin/lexicons \ GET /admin/lexicons ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface Lexicon { + id: string; + revision: number; + lexicon_type: string; + backfill: boolean; + created_at: string; + updated_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + headers, +}); +const data: Lexicon[] = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/lexicons") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/lexicons", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/lexicons -H "$AUTH" ``` @@ -74,7 +249,34 @@ curl http://127.0.0.1:3000/admin/lexicons -H "$AUTH" GET /admin/lexicons/{id} ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status", + { headers }, +); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status", + { headers }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status -H "$AUTH" ``` @@ -86,7 +288,37 @@ curl http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status -H "$AUTH" DELETE /admin/lexicons/{id} ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status", + { + method: "DELETE", + headers, + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status", + { + method: "DELETE", + headers, + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .delete("http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X DELETE http://127.0.0.1:3000/admin/lexicons/xyz.statusphere.status -H "$AUTH" ``` @@ -102,7 +334,63 @@ Network lexicons are fetched from the atproto network via DNS TXT resolution and POST /admin/network-lexicons ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface NetworkLexiconResult { + nsid: string; + authority_did: string; + revision: number; +} + +const response = await fetch("http://127.0.0.1:3000/admin/network-lexicons", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + nsid: "xyz.statusphere.status", + target_collection: null, + }), +}); +const data: NetworkLexiconResult = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/network-lexicons", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + nsid: "xyz.statusphere.status", + target_collection: null, + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/network-lexicons") + .bearer_auth(token) + .json(&serde_json::json!({ + "nsid": "xyz.statusphere.status", + "target_collection": null + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "nsid": "xyz.statusphere.status", + "target_collection": null +}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/network-lexicons", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/network-lexicons \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -135,7 +423,40 @@ HappyView resolves the NSID authority via DNS TXT, fetches the lexicon from the GET /admin/network-lexicons ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface NetworkLexicon { + nsid: string; + authority_did: string; + target_collection: string | null; + last_fetched_at: string; + created_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/network-lexicons", { + headers, +}); +const data: NetworkLexicon[] = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/network-lexicons", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/network-lexicons") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/network-lexicons", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/network-lexicons -H "$AUTH" ``` @@ -159,7 +480,37 @@ curl http://127.0.0.1:3000/admin/network-lexicons -H "$AUTH" DELETE /admin/network-lexicons/{nsid} ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/network-lexicons/xyz.statusphere.status", + { + method: "DELETE", + headers, + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/network-lexicons/xyz.statusphere.status", + { + method: "DELETE", + headers, + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .delete("http://127.0.0.1:3000/admin/network-lexicons/xyz.statusphere.status") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/network-lexicons/xyz.statusphere.status", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X DELETE http://127.0.0.1:3000/admin/network-lexicons/xyz.statusphere.status \ -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/plugins.md b/packages/docs/content/docs/api-reference/admin/plugins.md index 343450a..2bf0907 100644 --- a/packages/docs/content/docs/api-reference/admin/plugins.md +++ b/packages/docs/content/docs/api-reference/admin/plugins.md @@ -4,7 +4,21 @@ title: "Plugins" Plugins extend HappyView with WebAssembly modules sourced from the [official plugin registry](../../guides/plugins.md) or any URL serving a `manifest.json`. Most endpoints take a plugin manifest URL and load (or reload) the plugin in place — no restart needed. Encrypted plugin secrets require `TOKEN_ENCRYPTION_KEY` to be configured. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -17,7 +31,60 @@ GET /admin/plugins Requires `plugins:read`. Returns every loaded plugin with its source, required secrets, configuration status, and any pending updates from the official registry cache. -```sh +```ts tab="TypeScript" tab-group="language" +interface RequiredSecret { + key: string; + name: string; + description: string; +} + +interface PluginSummary { + id: string; + name: string; + version: string; + source: string; + url: string; + sha256: string | null; + enabled: boolean; + auth_type: string; + required_secrets: RequiredSecret[]; + secrets_configured: boolean; + loaded_at: string | null; + update_available: boolean; + latest_version: string; + pending_releases: string[]; +} + +interface PluginsResponse { + encryption_configured: boolean; + plugins: PluginSummary[]; +} + +const response = await fetch("http://127.0.0.1:3000/admin/plugins", { + headers, +}); +const data: PluginsResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/plugins", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/plugins") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/plugins", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/plugins -H "$AUTH" ``` @@ -63,7 +130,63 @@ POST /admin/plugins/preview Requires `plugins:create`. Fetches and parses a manifest without installing the plugin, so the dashboard can show what it would register. -```sh +```ts tab="TypeScript" tab-group="language" +interface PluginPreview { + id: string; + name: string; + version: string; + description: string; + icon_url: string; + auth_type: string; + required_secrets: RequiredSecret[]; + manifest_url: string; + wasm_url: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/plugins/preview", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: "https://example.com/plugins/steam/manifest.json", + }), +}); +const data: PluginPreview = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/plugins/preview", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: "https://example.com/plugins/steam/manifest.json", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/plugins/preview") + .bearer_auth(token) + .json(&serde_json::json!({ + "url": "https://example.com/plugins/steam/manifest.json" + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ "url": "https://example.com/plugins/steam/manifest.json" }`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/plugins/preview", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/plugins/preview \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -98,7 +221,57 @@ POST /admin/plugins Requires `plugins:create`. Fetches the manifest, downloads the WASM, registers the plugin, and persists it. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/plugins", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: "https://example.com/plugins/steam/manifest.json", + sha256: "abc123...", + }), +}); +const data: PluginSummary = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/plugins", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: "https://example.com/plugins/steam/manifest.json", + sha256: "abc123...", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/plugins") + .bearer_auth(token) + .json(&serde_json::json!({ + "url": "https://example.com/plugins/steam/manifest.json", + "sha256": "abc123..." + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "url": "https://example.com/plugins/steam/manifest.json", + "sha256": "abc123..." +}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/plugins", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/plugins \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -206,7 +379,58 @@ PUT /admin/plugins/{id}/secrets Requires `plugins:create`. Encrypts the provided secret values with `TOKEN_ENCRYPTION_KEY` (AES-256-GCM) and upserts them into `plugin_configs`. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/plugins/steam/secrets", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + secrets: { + PLUGIN_STEAM_API_KEY: "your-new-api-key", + }, + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/plugins/steam/secrets", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + secrets: { + PLUGIN_STEAM_API_KEY: "your-new-api-key", + }, + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .put("http://127.0.0.1:3000/admin/plugins/steam/secrets") + .bearer_auth(token) + .json(&serde_json::json!({ + "secrets": { + "PLUGIN_STEAM_API_KEY": "your-new-api-key" + } + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "secrets": { + "PLUGIN_STEAM_API_KEY": "your-new-api-key" + } +}`) +req, _ := http.NewRequest("PUT", "http://127.0.0.1:3000/admin/plugins/steam/secrets", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X PUT http://127.0.0.1:3000/admin/plugins/steam/secrets \ -H "$AUTH" \ -H "Content-Type: application/json" \ diff --git a/packages/docs/content/docs/api-reference/admin/script-variables.md b/packages/docs/content/docs/api-reference/admin/script-variables.md index 2b0f51d..5d7f723 100644 --- a/packages/docs/content/docs/api-reference/admin/script-variables.md +++ b/packages/docs/content/docs/api-reference/admin/script-variables.md @@ -4,7 +4,21 @@ title: "Script Variables" Script variables are encrypted key/value pairs available to Lua scripts via the `vars` global. Use them for secrets like API tokens. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -25,7 +39,42 @@ POST /admin/script-variables Requires `script-variables:create`. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/script-variables", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ key: "ALGOLIA_API_KEY", value: "..." }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/script-variables", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ key: "ALGOLIA_API_KEY", value: "..." }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/script-variables") + .bearer_auth(token) + .json(&serde_json::json!({ "key": "ALGOLIA_API_KEY", "value": "..." })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ "key": "ALGOLIA_API_KEY", "value": "..." }`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/script-variables", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/script-variables \ -H "$AUTH" \ -H "Content-Type: application/json" \ diff --git a/packages/docs/content/docs/api-reference/admin/settings.md b/packages/docs/content/docs/api-reference/admin/settings.md index 7c975e4..189e40d 100644 --- a/packages/docs/content/docs/api-reference/admin/settings.md +++ b/packages/docs/content/docs/api-reference/admin/settings.md @@ -4,7 +4,21 @@ title: "Instance Settings" Instance settings override environment variables at runtime — things like app name, ToS URL, privacy policy URL, and logo. Settings stored here take precedence over their env var equivalents. All endpoints require the `settings:manage` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -15,7 +29,32 @@ AUTH="Authorization: Bearer $TOKEN" GET /admin/settings ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings", { + headers, +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/settings") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/settings", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/settings -H "$AUTH" ``` @@ -27,7 +66,42 @@ Returns all key/value pairs stored in the `instance_settings` table. PUT /admin/settings/{key} ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings/app_name", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ value: "My HappyView" }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings/app_name", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ value: "My HappyView" }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .put("http://127.0.0.1:3000/admin/settings/app_name") + .bearer_auth(token) + .json(&serde_json::json!({ "value": "My HappyView" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ "value": "My HappyView" }`) +req, _ := http.NewRequest("PUT", "http://127.0.0.1:3000/admin/settings/app_name", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X PUT http://127.0.0.1:3000/admin/settings/app_name \ -H "$AUTH" \ -H "Content-Type: application/json" \ diff --git a/packages/docs/content/docs/api-reference/admin/stats.md b/packages/docs/content/docs/api-reference/admin/stats.md index 64c005c..7ccf12e 100644 --- a/packages/docs/content/docs/api-reference/admin/stats.md +++ b/packages/docs/content/docs/api-reference/admin/stats.md @@ -2,7 +2,21 @@ title: "Stats" --- -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -13,7 +27,43 @@ AUTH="Authorization: Bearer $TOKEN" GET /admin/stats ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface CollectionCount { + collection: string; + count: number; +} + +interface Stats { + total_records: number; + collections: CollectionCount[]; +} + +const response = await fetch("http://127.0.0.1:3000/admin/stats", { + headers, +}); +const data: Stats = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/stats", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let response = client + .get("http://127.0.0.1:3000/admin/stats") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/stats", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/stats -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/users.md b/packages/docs/content/docs/api-reference/admin/users.md index 383d8d8..3c42c5a 100644 --- a/packages/docs/content/docs/api-reference/admin/users.md +++ b/packages/docs/content/docs/api-reference/admin/users.md @@ -4,7 +4,21 @@ title: "Users" Manage admin users and their permissions. See the [Permissions guide](../../guides/permissions.md) for available permissions and templates. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -17,7 +31,64 @@ POST /admin/users Requires `users:create` permission. You cannot grant permissions you don't have yourself (escalation guard). -```sh +```ts tab="TypeScript" tab-group="language" +interface User { + id: string; + did: string; + is_super: boolean; + permissions: string[]; +} + +const response = await fetch("http://127.0.0.1:3000/admin/users", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + did: "did:plc:newuser", + template: "operator", + }), +}); +const data: User = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/users", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + did: "did:plc:newuser", + template: "operator", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/users") + .bearer_auth(token) + .json(&serde_json::json!({ + "did": "did:plc:newuser", + "template": "operator" + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "did": "did:plc:newuser", + "template": "operator" +}`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/users", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/users \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -54,7 +125,41 @@ GET /admin/users Requires `users:read` permission. -```sh +```ts tab="TypeScript" tab-group="language" +interface UserWithTimestamps { + id: string; + did: string; + is_super: boolean; + permissions: string[]; + created_at: string; + last_used_at: string; +} + +const response = await fetch("http://127.0.0.1:3000/admin/users", { + headers, +}); +const data: UserWithTimestamps[] = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/users", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/users") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/users", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/users -H "$AUTH" ``` @@ -81,7 +186,34 @@ GET /admin/users/{id} Requires `users:read` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000", + { headers }, +); +const data: UserWithTimestamps = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000", + { headers }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000 -H "$AUTH" ``` @@ -95,7 +227,63 @@ PATCH /admin/users/{id}/permissions Requires `users:update` permission. You cannot grant permissions you don't have yourself, and you cannot modify the super user's permissions. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000/permissions", + { + method: "PATCH", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + grant: ["lexicons:create", "lexicons:delete"], + revoke: ["records:delete"], + }), + }, +); +const data: User = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000/permissions", + { + method: "PATCH", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + grant: ["lexicons:create", "lexicons:delete"], + revoke: ["records:delete"], + }), + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .patch("http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000/permissions") + .bearer_auth(token) + .json(&serde_json::json!({ + "grant": ["lexicons:create", "lexicons:delete"], + "revoke": ["records:delete"] + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "grant": ["lexicons:create", "lexicons:delete"], + "revoke": ["records:delete"] +}`) +req, _ := http.NewRequest("PATCH", "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000/permissions", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X PATCH http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000/permissions \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -120,7 +308,48 @@ POST /admin/users/transfer-super Only the current super user can call this endpoint. Transfers super user status to another existing user. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/users/transfer-super", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + target_user_id: "550e8400-e29b-41d4-a716-446655440000", + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/users/transfer-super", { + method: "POST", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + target_user_id: "550e8400-e29b-41d4-a716-446655440000", + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/users/transfer-super") + .bearer_auth(token) + .json(&serde_json::json!({ + "target_user_id": "550e8400-e29b-41d4-a716-446655440000" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ "target_user_id": "550e8400-e29b-41d4-a716-446655440000" }`) +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/users/transfer-super", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/users/transfer-super \ -H "$AUTH" \ -H "Content-Type: application/json" \ @@ -141,7 +370,37 @@ DELETE /admin/users/{id} Requires `users:delete` permission. You cannot delete the super user or yourself. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000", + { + method: "DELETE", + headers, + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000", + { + method: "DELETE", + headers, + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .delete("http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X DELETE http://127.0.0.1:3000/admin/users/550e8400-e29b-41d4-a716-446655440000 \ -H "$AUTH" ``` diff --git a/packages/docs/content/docs/api-reference/admin/xrpc-proxy.md b/packages/docs/content/docs/api-reference/admin/xrpc-proxy.md index bce8faa..ff0b031 100644 --- a/packages/docs/content/docs/api-reference/admin/xrpc-proxy.md +++ b/packages/docs/content/docs/api-reference/admin/xrpc-proxy.md @@ -6,7 +6,21 @@ Control which unrecognized XRPC methods HappyView forwards to their resolved aut All endpoints require the `settings:manage` permission. -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; +``` +```rust tab="Rust" tab-group="language" +let token = "hv_..."; // your API key +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +``` +```sh tab="cURL" tab-group="language" # All examples assume $TOKEN is an API key (hv_...) AUTH="Authorization: Bearer $TOKEN" ``` @@ -17,7 +31,37 @@ AUTH="Authorization: Bearer $TOKEN" GET /admin/settings/xrpc-proxy ``` -```sh +```ts tab="TypeScript" tab-group="language" +interface XrpcProxyConfig { + mode: string; + nsids: string[]; +} + +const response = await fetch("http://127.0.0.1:3000/admin/settings/xrpc-proxy", { + headers, +}); +const data: XrpcProxyConfig = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings/xrpc-proxy", { + headers, +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/admin/settings/xrpc-proxy") + .bearer_auth(token) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/settings/xrpc-proxy", nil) +req.Header.Set("Authorization", "Bearer "+token) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/settings/xrpc-proxy -H "$AUTH" ``` @@ -38,7 +82,54 @@ Returns `{"mode": "open", "nsids": []}` when no config has been saved. PUT /admin/settings/xrpc-proxy ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings/xrpc-proxy", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + mode: "allowlist", + nsids: ["com.example.feed.*"], + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings/xrpc-proxy", { + method: "PUT", + headers: { + ...headers, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + mode: "allowlist", + nsids: ["com.example.feed.*"], + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .put("http://127.0.0.1:3000/admin/settings/xrpc-proxy") + .bearer_auth(token) + .json(&serde_json::json!({ + "mode": "allowlist", + "nsids": ["com.example.feed.*"] + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "mode": "allowlist", + "nsids": ["com.example.feed.*"] +}`) +req, _ := http.NewRequest("PUT", "http://127.0.0.1:3000/admin/settings/xrpc-proxy", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X PUT http://127.0.0.1:3000/admin/settings/xrpc-proxy \ -H "$AUTH" \ -H "Content-Type: application/json" \ diff --git a/packages/docs/content/docs/api-reference/oauth/api-clients.md b/packages/docs/content/docs/api-reference/oauth/api-clients.md index 95cdd3d..0660409 100644 --- a/packages/docs/content/docs/api-reference/oauth/api-clients.md +++ b/packages/docs/content/docs/api-reference/oauth/api-clients.md @@ -90,7 +90,123 @@ Returns `404` if the client doesn't exist or isn't owned by the authenticated us POST /xrpc/dev.happyview.createApiClient ``` -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_parent_key"; // parent API client key +const ACCESS_TOKEN = "eyJhbG..."; // DPoP access token +const DPOP_PROOF = "eyJhbG..."; // DPoP proof JWT + +interface CreateClientResponse { + client: { + id: string; + clientKey: string; + name: string; + clientIdUrl: string; + clientUri: string; + redirectUris: string[]; + clientType: string; + scopes: string; + allowedOrigins: string[]; + isActive: boolean; + createdAt: string; + }; + clientSecret?: string; +} + +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.createApiClient", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "My Third-Party App", + clientIdUrl: "https://myapp.example.com/client-metadata.json", + clientUri: "https://myapp.example.com", + redirectUris: ["https://myapp.example.com/callback"], + clientType: "confidential", + }), + }, +); + +const data: CreateClientResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_parent_key"; // parent API client key +const ACCESS_TOKEN = "eyJhbG..."; // DPoP access token +const DPOP_PROOF = "eyJhbG..."; // DPoP proof JWT + +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.createApiClient", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "My Third-Party App", + clientIdUrl: "https://myapp.example.com/client-metadata.json", + clientUri: "https://myapp.example.com", + redirectUris: ["https://myapp.example.com/callback"], + clientType: "confidential", + }), + }, +); + +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let client_key = "hvc_parent_key"; // parent API client key +let access_token = "eyJhbG..."; // DPoP access token +let dpop_proof = "eyJhbG..."; // DPoP proof JWT + +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.createApiClient") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ + "name": "My Third-Party App", + "clientIdUrl": "https://myapp.example.com/client-metadata.json", + "clientUri": "https://myapp.example.com", + "redirectUris": ["https://myapp.example.com/callback"], + "clientType": "confidential" + })) + .send() + .await?; + +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_parent_key" // parent API client key +accessToken := "eyJhbG..." // DPoP access token +dpopProof := "eyJhbG..." // DPoP proof JWT + +body := bytes.NewBufferString(`{ + "name": "My Third-Party App", + "clientIdUrl": "https://myapp.example.com/client-metadata.json", + "clientUri": "https://myapp.example.com", + "redirectUris": ["https://myapp.example.com/callback"], + "clientType": "confidential" +}`) + +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.createApiClient", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST https://happyview.example.com/xrpc/dev.happyview.createApiClient \ -H "X-Client-Key: hvc_parent_key" \ -H "Authorization: DPoP eyJhbG..." \ @@ -144,7 +260,65 @@ The `clientSecret` is only present for confidential clients and is only returned POST /xrpc/dev.happyview.deleteApiClient ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.deleteApiClient", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: "550e8400-e29b-41d4-a716-446655440000", + }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.deleteApiClient", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: "550e8400-e29b-41d4-a716-446655440000", + }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.deleteApiClient") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ + "id": "550e8400-e29b-41d4-a716-446655440000" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"id": "550e8400-e29b-41d4-a716-446655440000"}`) + +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.deleteApiClient", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST https://happyview.example.com/xrpc/dev.happyview.deleteApiClient \ -H "X-Client-Key: hvc_parent_key" \ -H "Authorization: DPoP eyJhbG..." \ diff --git a/packages/docs/content/docs/api-reference/xrpc-api.md b/packages/docs/content/docs/api-reference/xrpc-api.md index d4c1adf..3db76a5 100644 --- a/packages/docs/content/docs/api-reference/xrpc-api.md +++ b/packages/docs/content/docs/api-reference/xrpc-api.md @@ -23,7 +23,28 @@ These endpoints are always available regardless of which lexicons are loaded. GET /health ``` -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/health"); +const text = await response.text(); // "ok" +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/health"); +const text = await response.text(); // "ok" +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); + +let response = client + .get("http://127.0.0.1:3000/health") + .send() + .await?; + +let text = response.text().await?; // "ok" +``` +```go tab="Go" tab-group="language" +resp, err := http.Get("http://127.0.0.1:3000/health") +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/health ``` @@ -37,7 +58,71 @@ GET /xrpc/app.bsky.actor.getProfile Returns the authenticated user's profile, resolved from their PDS via PLC directory lookup. -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const TOKEN = "..."; // your access token + +interface ProfileResponse { + did: string; + handle: string; + displayName: string; + description: string; + avatarURL: string; +} + +const response = await fetch( + "http://127.0.0.1:3000/xrpc/app.bsky.actor.getProfile", + { + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + }, + }, +); + +const profile: ProfileResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const TOKEN = "..."; // your access token + +const response = await fetch( + "http://127.0.0.1:3000/xrpc/app.bsky.actor.getProfile", + { + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + }, + }, +); + +const profile = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client_key = "hvc_..."; // your API client key +let token = "..."; // your access token + +let response = client + .get("http://127.0.0.1:3000/xrpc/app.bsky.actor.getProfile") + .header("X-Client-Key", client_key) + .bearer_auth(token) + .send() + .await?; + +let profile: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_..." // your API client key +token := "..." // your access token + +req, _ := http.NewRequest("GET", + "http://127.0.0.1:3000/xrpc/app.bsky.actor.getProfile", nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/xrpc/app.bsky.actor.getProfile \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" @@ -63,7 +148,67 @@ POST /xrpc/com.atproto.repo.uploadBlob Proxies a blob upload to the authenticated user's PDS. Maximum size: 50MB. -```sh +```ts tab="TypeScript" tab-group="language" +import { readFile } from "node:fs/promises"; + +const imageData = await readFile("image.png"); + +const response = await fetch( + "http://127.0.0.1:3000/xrpc/com.atproto.repo.uploadBlob", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "image/png", + }, + body: imageData, + }, +); +``` +```js tab="JavaScript" tab-group="language" +import { readFile } from "node:fs/promises"; + +const imageData = await readFile("image.png"); + +const response = await fetch( + "http://127.0.0.1:3000/xrpc/com.atproto.repo.uploadBlob", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "image/png", + }, + body: imageData, + }, +); +``` +```rust tab="Rust" tab-group="language" +let image_data = std::fs::read("image.png")?; + +let response = client + .post("http://127.0.0.1:3000/xrpc/com.atproto.repo.uploadBlob") + .header("X-Client-Key", client_key) + .bearer_auth(token) + .header("Content-Type", "image/png") + .body(image_data) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +imageData, _ := os.ReadFile("image.png") + +req, _ := http.NewRequest("POST", + "http://127.0.0.1:3000/xrpc/com.atproto.repo.uploadBlob", + bytes.NewReader(imageData)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "image/png") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/com.atproto.repo.uploadBlob \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ @@ -83,7 +228,66 @@ Query endpoints are generated from lexicons with `type: "query"`. Without a [Lua GET /xrpc/{method}?uri={at-uri} ``` -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key + +const params = new URLSearchParams({ + uri: "at://did:plc:abc/xyz.statusphere.status/abc123", +}); + +interface RecordResponse { + record: { + uri: string; + $type: string; + status: string; + createdAt: string; + }; +} + +const response = await fetch( + `http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?${params}`, + { headers: { "X-Client-Key": CLIENT_KEY } }, +); + +const data: RecordResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key + +const params = new URLSearchParams({ + uri: "at://did:plc:abc/xyz.statusphere.status/abc123", +}); + +const response = await fetch( + `http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?${params}`, + { headers: { "X-Client-Key": CLIENT_KEY } }, +); + +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client_key = "hvc_..."; // your API client key + +let response = client + .get("http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses") + .query(&[("uri", "at://did:plc:abc/xyz.statusphere.status/abc123")]) + .header("X-Client-Key", client_key) + .send() + .await?; + +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_..." // your API client key + +req, _ := http.NewRequest("GET", + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?uri=at%3A%2F%2Fdid%3Aplc%3Aabc%2Fxyz.statusphere.status%2Fabc123", + nil) +req.Header.Set("X-Client-Key", clientKey) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?uri=at%3A%2F%2Fdid%3Aplc%3Aabc%2Fxyz.statusphere.status%2Fabc123" \ -H "X-Client-Key: $CLIENT_KEY" ``` @@ -115,7 +319,54 @@ GET /xrpc/{method}?limit=20&cursor=&did=optional | `cursor` | string | --- | Opaque pagination cursor from a previous response | | `did` | string | --- | Filter records by DID | -```sh +```ts tab="TypeScript" tab-group="language" +const params = new URLSearchParams({ limit: "10", did: "did:plc:abc" }); + +interface ListResponse { + records: Array<{ + uri: string; + status: string; + createdAt: string; + }>; + cursor?: string; +} + +const response = await fetch( + `http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?${params}`, + { headers: { "X-Client-Key": CLIENT_KEY } }, +); + +const data: ListResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const params = new URLSearchParams({ limit: "10", did: "did:plc:abc" }); + +const response = await fetch( + `http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?${params}`, + { headers: { "X-Client-Key": CLIENT_KEY } }, +); + +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses") + .query(&[("limit", "10"), ("did", "did:plc:abc")]) + .header("X-Client-Key", client_key) + .send() + .await?; + +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=10&did=did:plc:abc", + nil) +req.Header.Set("X-Client-Key", clientKey) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=10&did=did:plc:abc" \ -H "X-Client-Key: $CLIENT_KEY" ``` @@ -149,10 +400,88 @@ POST /xrpc/{method} When the body does **not** contain a `uri` field, a new record is created. -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + status: "\ud83d\ude0a", + createdAt: "2025-01-01T12:00:00Z", + }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + status: "\ud83d\ude0a", + createdAt: "2025-01-01T12:00:00Z", + }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let client_key = "hvc_..."; // your API client key +let access_token = "..."; // DPoP access token +let dpop_proof = "..."; // DPoP proof JWT + +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ + "status": "\ud83d\ude0a", + "createdAt": "2025-01-01T12:00:00Z" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_..." // your API client key +accessToken := "..." // DPoP access token +dpopProof := "..." // DPoP proof JWT + +body := bytes.NewBufferString(`{"status": "\ud83d\ude0a", "createdAt": "2025-01-01T12:00:00Z"}`) + +req, _ := http.NewRequest("POST", + "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus \ -H "X-Client-Key: $CLIENT_KEY" \ - -H "Authorization: Bearer $TOKEN" \ + -H "Authorization: DPoP $ACCESS_TOKEN" \ + -H "DPoP: $DPOP_PROOF" \ -H "Content-Type: application/json" \ -d '{ "status": "\ud83d\ude0a", "createdAt": "2025-01-01T12:00:00Z" }' ``` @@ -163,10 +492,79 @@ HappyView proxies this to the user's PDS as `com.atproto.repo.createRecord`, the When the body **contains** a `uri` field, the existing record is updated. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.status/abc123", + status: "\ud83c\udf1f", + createdAt: "2025-01-01T13:00:00Z", + }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.status/abc123", + status: "\ud83c\udf1f", + createdAt: "2025-01-01T13:00:00Z", + }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ + "uri": "at://did:plc:abc/xyz.statusphere.status/abc123", + "status": "\ud83c\udf1f", + "createdAt": "2025-01-01T13:00:00Z" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "uri": "at://did:plc:abc/xyz.statusphere.status/abc123", + "status": "\ud83c\udf1f", + "createdAt": "2025-01-01T13:00:00Z" +}`) + +req, _ := http.NewRequest("POST", + "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus \ -H "X-Client-Key: $CLIENT_KEY" \ - -H "Authorization: Bearer $TOKEN" \ + -H "Authorization: DPoP $ACCESS_TOKEN" \ + -H "DPoP: $DPOP_PROOF" \ -H "Content-Type: application/json" \ -d '{ "uri": "at://did:plc:abc/xyz.statusphere.status/abc123", diff --git a/packages/docs/content/docs/experimental/spaces/credentials.md b/packages/docs/content/docs/experimental/spaces/credentials.md index 9049622..a20bdcc 100644 --- a/packages/docs/content/docs/experimental/spaces/credentials.md +++ b/packages/docs/content/docs/experimental/spaces/credentials.md @@ -38,7 +38,64 @@ Credentials are ES256 JWTs signed with a P-256 keypair unique to each space. The The caller must be an authenticated member of the space. The grant is a short-lived token (5 minutes) that proves membership. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.getMemberGrant", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + }), +}); +interface GrantResponse { + grant: string; + expiresAt: string; +} +const data: GrantResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.getMemberGrant", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.getMemberGrant") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main" + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"space": "ats://did:plc:abc123/com.example.forum/main"}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.getMemberGrant", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.getMemberGrant' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -62,7 +119,64 @@ curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.getMemberGr Exchange the grant for a space credential JWT. The credential is signed by the space's keypair and has a 4-hour TTL. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.getSpaceCredential", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + grant: "eyJhbGciOiJIUzI1NiJ9...", + }), +}); +interface CredentialResponse { + credential: string; + expiresAt: string; +} +const data: CredentialResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.getSpaceCredential", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + grant: "eyJhbGciOiJIUzI1NiJ9...", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.getSpaceCredential") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "grant": "eyJhbGciOiJIUzI1NiJ9..." + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"grant": "eyJhbGciOiJIUzI1NiJ9..."}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.getSpaceCredential", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.getSpaceCredential' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -99,7 +213,45 @@ The JWT payload contains: Pass the credential as a standard Bearer token in the `Authorization` header. HappyView distinguishes space credentials from other tokens by checking the JWT header's `typ` field (`space_credential`). -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...", + { + headers: { + "Authorization": `Bearer ${SPACE_CREDENTIAL}`, + }, + }, +); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...", + { + headers: { + "Authorization": `Bearer ${SPACE_CREDENTIAL}`, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.getRecord") + .query(&[("space", "..."), ("collection", "..."), ("rkey", "...")]) + .header("Authorization", format!("Bearer {}", space_credential)) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...", + nil) +req.Header.Set("Authorization", "Bearer "+spaceCredential) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...' \ -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6InNwYWNlX2NyZWRlbnRpYWwifQ...' ``` diff --git a/packages/docs/content/docs/experimental/spaces/index.md b/packages/docs/content/docs/experimental/spaces/index.md index e8456fb..803c99b 100644 --- a/packages/docs/content/docs/experimental/spaces/index.md +++ b/packages/docs/content/docs/experimental/spaces/index.md @@ -28,7 +28,43 @@ ats://///// In HappyView, spaces are gated behind the `feature.spaces_enabled` instance setting. Enable it in the dashboard under **Settings** or via the admin API: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings/feature.spaces_enabled", { + method: "PUT", + headers: { + "Authorization": `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ value: "true" }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/settings/feature.spaces_enabled", { + method: "PUT", + headers: { + "Authorization": `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ value: "true" }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .put("http://127.0.0.1:3000/admin/settings/feature.spaces_enabled") + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ "value": "true" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"value": "true"}`) +req, _ := http.NewRequest("PUT", + "http://127.0.0.1:3000/admin/settings/feature.spaces_enabled", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X PUT http://127.0.0.1:3000/admin/settings/feature.spaces_enabled \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ diff --git a/packages/docs/content/docs/experimental/spaces/invites.md b/packages/docs/content/docs/experimental/spaces/invites.md index d4c37c5..6c48bbe 100644 --- a/packages/docs/content/docs/experimental/spaces/invites.md +++ b/packages/docs/content/docs/experimental/spaces/invites.md @@ -16,7 +16,81 @@ Invites are a HappyView-specific feature, not part of the AT Protocol spaces spe Only the space owner or a super admin can create invites. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.createInvite", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + access: "write", + maxUses: 10, + expiresAt: "2026-06-01T00:00:00Z", + }), +}); +interface CreateInviteResponse { + inviteId: string; + token: string; + access: string; + maxUses: number; + expiresAt: string; +} +const data: CreateInviteResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.createInvite", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + access: "write", + maxUses: 10, + expiresAt: "2026-06-01T00:00:00Z", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.createInvite") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "access": "write", + "maxUses": 10, + "expiresAt": "2026-06-01T00:00:00Z" + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "access": "write", + "maxUses": 10, + "expiresAt": "2026-06-01T00:00:00Z" +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.createInvite", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.createInvite' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -59,7 +133,64 @@ The `token` is only returned once. It is stored as a SHA-256 hash — HappyView Any authenticated user can redeem an invite token to join the space. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.redeemInvite", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + token: "a1b2c3d4e5f6...", + }), +}); +interface RedeemInviteResponse { + uri: string; + access: string; +} +const data: RedeemInviteResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.redeemInvite", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + token: "a1b2c3d4e5f6...", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.redeemInvite") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "token": "a1b2c3d4e5f6..." + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"token": "a1b2c3d4e5f6..."}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.redeemInvite", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.redeemInvite' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -89,7 +220,63 @@ Redemption fails if: ## Revoking an invite -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + inviteId: "uuid", + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + inviteId: "uuid", + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "inviteId": "uuid" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "inviteId": "uuid" +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -107,7 +294,63 @@ Revoking an invite prevents future redemptions but does not remove members who a Only the space owner or a super admin can list invites. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=ats://did:plc:abc123/com.example.forum/main", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +interface Invite { + id: string; + access: string; + maxUses: number; + uses: number; + expiresAt: string; + revoked: boolean; + createdBy: string; + createdAt: string; +} +const data: { invites: Invite[] } = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=ats://did:plc:abc123/com.example.forum/main", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.listInvites") + .query(&[("space", "ats://did:plc:abc123/com.example.forum/main")]) + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=ats://did:plc:abc123/com.example.forum/main", + nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=ats://did:plc:abc123/com.example.forum/main' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ diff --git a/packages/docs/content/docs/experimental/spaces/managing-spaces.md b/packages/docs/content/docs/experimental/spaces/managing-spaces.md index 581ce49..3287f3f 100644 --- a/packages/docs/content/docs/experimental/spaces/managing-spaces.md +++ b/packages/docs/content/docs/experimental/spaces/managing-spaces.md @@ -8,7 +8,81 @@ This API is experimental and will change. See the [Permissioned Spaces overview] ## Creating a space -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.createSpace", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + type: "com.example.forum", + skey: "main", + displayName: "My Forum", + description: "A place for discussion", + accessMode: "default_allow", + }), +}); +interface CreateSpaceResponse { + uri: string; +} +const data: CreateSpaceResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.createSpace", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + type: "com.example.forum", + skey: "main", + displayName: "My Forum", + description: "A place for discussion", + accessMode: "default_allow", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.createSpace") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "type": "com.example.forum", + "skey": "main", + "displayName": "My Forum", + "description": "A place for discussion", + "accessMode": "default_allow" + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "type": "com.example.forum", + "skey": "main", + "displayName": "My Forum", + "description": "A place for discussion", + "accessMode": "default_allow" +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.createSpace", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.createSpace' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -58,7 +132,57 @@ Additional fields are preserved as-is. ## Getting a space -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.getSpace?space=ats://did:plc:abc123/com.example.forum/main", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +interface Space { + uri: string; + isOwner: boolean; +} +const data: Space = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.getSpace?space=ats://did:plc:abc123/com.example.forum/main", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.getSpace") + .query(&[("space", "ats://did:plc:abc123/com.example.forum/main")]) + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.getSpace?space=ats://did:plc:abc123/com.example.forum/main", + nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.getSpace?space=ats://did:plc:abc123/com.example.forum/main' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -71,7 +195,61 @@ If `membershipPublic` is `false`, the caller must be authenticated and be a memb Returns spaces where the authenticated user is a member. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.listSpaces?limit=20", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +interface Space { + uri: string; + isOwner: boolean; +} +interface ListSpacesResponse { + spaces: Space[]; + cursor?: string; +} +const data: ListSpacesResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.listSpaces?limit=20", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.listSpaces") + .query(&[("limit", "20")]) + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.listSpaces?limit=20", + nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.listSpaces?limit=20' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -103,7 +281,71 @@ curl 'https://happyview.example.com/xrpc/dev.happyview.space.listSpaces?limit=20 Only the space owner or a HappView super admin can update a space. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.updateSpace", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + displayName: "Updated Forum Name", + accessMode: "default_deny", + appAllowlist: ["did:web:myapp.example.com"], + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.updateSpace", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + displayName: "Updated Forum Name", + accessMode: "default_deny", + appAllowlist: ["did:web:myapp.example.com"], + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.updateSpace") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "displayName": "Updated Forum Name", + "accessMode": "default_deny", + "appAllowlist": ["did:web:myapp.example.com"] + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "displayName": "Updated Forum Name", + "accessMode": "default_deny", + "appAllowlist": ["did:web:myapp.example.com"] +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.updateSpace", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.updateSpace' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -123,7 +365,57 @@ All fields except `space` are optional. Only provided fields are updated. To cle Only the space owner or a HappyView super admin can delete a space. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.deleteSpace", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.deleteSpace", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.deleteSpace") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"space": "ats://did:plc:abc123/com.example.forum/main"}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.deleteSpace", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.deleteSpace' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ diff --git a/packages/docs/content/docs/experimental/spaces/members.md b/packages/docs/content/docs/experimental/spaces/members.md index 43d3c3d..30bf6e6 100644 --- a/packages/docs/content/docs/experimental/spaces/members.md +++ b/packages/docs/content/docs/experimental/spaces/members.md @@ -12,7 +12,83 @@ Membership determines who can read and write within a space. Members have either Only the space owner or a super admin can add members. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.addMember", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + did: "did:plc:newmember", + access: "write", + isDelegation: false, + }), +}); +interface Member { + id: string; + spaceId: string; + did: string; + access: string; + isDelegation: boolean; + grantedBy: string; + createdAt: string; +} +const data: { member: Member } = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.addMember", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + did: "did:plc:newmember", + access: "write", + isDelegation: false, + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.addMember") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "did": "did:plc:newmember", + "access": "write", + "isDelegation": false + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "did": "did:plc:newmember", + "access": "write", + "isDelegation": false +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.addMember", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.addMember' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -53,7 +129,63 @@ curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.addMember' ## Removing a member -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.removeMember", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + did: "did:plc:newmember", + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.removeMember", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + did: "did:plc:newmember", + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.removeMember") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "did": "did:plc:newmember" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "did": "did:plc:newmember" +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.removeMember", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.removeMember' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -67,7 +199,57 @@ curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.removeMembe ## Listing members -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.listMembers?space=ats://did:plc:abc123/com.example.forum/main", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +interface ResolvedMember { + did: string; + access: string; +} +const data: { members: ResolvedMember[] } = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.listMembers?space=ats://did:plc:abc123/com.example.forum/main", + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.listMembers") + .query(&[("space", "ats://did:plc:abc123/com.example.forum/main")]) + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.listMembers?space=ats://did:plc:abc123/com.example.forum/main", + nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.listMembers?space=ats://did:plc:abc123/com.example.forum/main' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -92,7 +274,71 @@ The response returns the **resolved** member list — delegation chains are trav A space can be added as a member of another space by setting `isDelegation: true`. This transitively grants access to all members of the delegated space. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.addMember", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + did: "ats://did:plc:org/com.example.team/engineering", + access: "read", + isDelegation: true, + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.addMember", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + did: "ats://did:plc:org/com.example.team/engineering", + access: "read", + isDelegation: true, + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.addMember") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "did": "ats://did:plc:org/com.example.team/engineering", + "access": "read", + "isDelegation": true + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "did": "ats://did:plc:org/com.example.team/engineering", + "access": "read", + "isDelegation": true +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.addMember", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.addMember' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ diff --git a/packages/docs/content/docs/experimental/spaces/records.md b/packages/docs/content/docs/experimental/spaces/records.md index a848ff7..efb8f69 100644 --- a/packages/docs/content/docs/experimental/spaces/records.md +++ b/packages/docs/content/docs/experimental/spaces/records.md @@ -17,7 +17,90 @@ ats:// did:plc:abcdefghijklmnop1234567890 / com.example.forum / main / di Requires `write` membership in the space. The rkey is auto-generated using a TID. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.createRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + record: { + $type: "com.example.forum.post", + text: "Hello from the forum!", + createdAt: "2026-05-09T12:00:00Z", + }, + }), +}); +interface CreateRecordResponse { + uri: string; + cid: string; +} +const data: CreateRecordResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.createRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + record: { + $type: "com.example.forum.post", + text: "Hello from the forum!", + createdAt: "2026-05-09T12:00:00Z", + }, + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.createRecord") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "collection": "com.example.forum.post", + "record": { + "$type": "com.example.forum.post", + "text": "Hello from the forum!", + "createdAt": "2026-05-09T12:00:00Z" + } + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "collection": "com.example.forum.post", + "record": { + "$type": "com.example.forum.post", + "text": "Hello from the forum!", + "createdAt": "2026-05-09T12:00:00Z" + } +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.createRecord", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.createRecord' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -57,7 +140,94 @@ curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.createRecor Requires `write` membership in the space. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.putRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + rkey: "3k2abc", + record: { + $type: "com.example.forum.post", + text: "Hello from the forum!", + createdAt: "2026-05-09T12:00:00Z", + }, + }), +}); +interface PutRecordResponse { + uri: string; + cid: string; +} +const data: PutRecordResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.putRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + rkey: "3k2abc", + record: { + $type: "com.example.forum.post", + text: "Hello from the forum!", + createdAt: "2026-05-09T12:00:00Z", + }, + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.putRecord") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "collection": "com.example.forum.post", + "rkey": "3k2abc", + "record": { + "$type": "com.example.forum.post", + "text": "Hello from the forum!", + "createdAt": "2026-05-09T12:00:00Z" + } + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "collection": "com.example.forum.post", + "rkey": "3k2abc", + "record": { + "$type": "com.example.forum.post", + "text": "Hello from the forum!", + "createdAt": "2026-05-09T12:00:00Z" + } +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.putRecord", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.putRecord' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -102,7 +272,72 @@ The author DID is taken from the authenticated user. You can only write records Requires `read` membership (or a valid [space credential](credentials.md)). -```sh +```ts tab="TypeScript" tab-group="language" +const params = new URLSearchParams({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + rkey: "3k2abc", +}); +const response = await fetch( + `https://happyview.example.com/xrpc/dev.happyview.space.getRecord?${params}`, + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +interface GetRecordResponse { + uri: string; + cid: string; + value: Record; +} +const data: GetRecordResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const params = new URLSearchParams({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + rkey: "3k2abc", +}); +const response = await fetch( + `https://happyview.example.com/xrpc/dev.happyview.space.getRecord?${params}`, + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.getRecord") + .query(&[ + ("space", "ats://did:plc:abc123/com.example.forum/main"), + ("collection", "com.example.forum.post"), + ("rkey", "3k2abc"), + ]) + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=ats://did:plc:abc123/com.example.forum/main&collection=com.example.forum.post&rkey=3k2abc", + nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=ats://did:plc:abc123/com.example.forum/main&collection=com.example.forum.post&rkey=3k2abc' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -133,7 +368,76 @@ curl 'https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=ats ## Listing records -```sh +```ts tab="TypeScript" tab-group="language" +const params = new URLSearchParams({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + limit: "20", +}); +const response = await fetch( + `https://happyview.example.com/xrpc/dev.happyview.space.listRecords?${params}`, + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +interface RecordEntry { + collection: string; + rkey: string; + cid: string; +} +interface ListRecordsResponse { + records: RecordEntry[]; + cursor?: string; +} +const data: ListRecordsResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const params = new URLSearchParams({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + limit: "20", +}); +const response = await fetch( + `https://happyview.example.com/xrpc/dev.happyview.space.listRecords?${params}`, + { + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.listRecords") + .query(&[ + ("space", "ats://did:plc:abc123/com.example.forum/main"), + ("collection", "com.example.forum.post"), + ("limit", "20"), + ]) + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.listRecords?space=ats://did:plc:abc123/com.example.forum/main&collection=com.example.forum.post&limit=20", + nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.listRecords?space=ats://did:plc:abc123/com.example.forum/main&collection=com.example.forum.post&limit=20' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -170,7 +474,67 @@ curl 'https://happyview.example.com/xrpc/dev.happyview.space.listRecords?space=a You can only delete your own records. Requires `write` membership. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.deleteRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + rkey: "3k2abc", + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.deleteRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + collection: "com.example.forum.post", + rkey: "3k2abc", + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.deleteRecord") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "collection": "com.example.forum.post", + "rkey": "3k2abc" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "collection": "com.example.forum.post", + "rkey": "3k2abc" +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.deleteRecord", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.deleteRecord' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -198,7 +562,142 @@ Attempting to delete another user's record returns `403 Forbidden`. `applyWrites` performs multiple create, update, and delete operations in a single request. Requires `write` membership. -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.applyWrites", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + writes: [ + { + action: "create", + collection: "com.example.forum.post", + value: { $type: "com.example.forum.post", text: "First post" }, + }, + { + action: "update", + collection: "com.example.forum.post", + rkey: "3k2abc", + value: { $type: "com.example.forum.post", text: "Edited post" }, + swapRecord: "bafyrei...", + }, + { + action: "delete", + collection: "com.example.forum.post", + rkey: "old-post", + }, + ], + }), +}); +interface ApplyWritesResult { + uri?: string; + cid?: string; +} +const data: { results: ApplyWritesResult[] } = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.applyWrites", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + "Authorization": `DPoP ${ACCESS_TOKEN}`, + "DPoP": DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + space: "ats://did:plc:abc123/com.example.forum/main", + writes: [ + { + action: "create", + collection: "com.example.forum.post", + value: { $type: "com.example.forum.post", text: "First post" }, + }, + { + action: "update", + collection: "com.example.forum.post", + rkey: "3k2abc", + value: { $type: "com.example.forum.post", text: "Edited post" }, + swapRecord: "bafyrei...", + }, + { + action: "delete", + collection: "com.example.forum.post", + rkey: "old-post", + }, + ], + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("https://happyview.example.com/xrpc/dev.happyview.space.applyWrites") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", &dpop_proof) + .json(&serde_json::json!({ + "space": "ats://did:plc:abc123/com.example.forum/main", + "writes": [ + { + "action": "create", + "collection": "com.example.forum.post", + "value": { "$type": "com.example.forum.post", "text": "First post" } + }, + { + "action": "update", + "collection": "com.example.forum.post", + "rkey": "3k2abc", + "value": { "$type": "com.example.forum.post", "text": "Edited post" }, + "swapRecord": "bafyrei..." + }, + { + "action": "delete", + "collection": "com.example.forum.post", + "rkey": "old-post" + } + ] + })) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{ + "space": "ats://did:plc:abc123/com.example.forum/main", + "writes": [ + { + "action": "create", + "collection": "com.example.forum.post", + "value": { "$type": "com.example.forum.post", "text": "First post" } + }, + { + "action": "update", + "collection": "com.example.forum.post", + "rkey": "3k2abc", + "value": { "$type": "com.example.forum.post", "text": "Edited post" }, + "swapRecord": "bafyrei..." + }, + { + "action": "delete", + "collection": "com.example.forum.post", + "rkey": "old-post" + } + ] +}`) +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/dev.happyview.space.applyWrites", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.applyWrites' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -294,7 +793,45 @@ The space's current revision is available as `revision` in the space object retu Records can also be read using a [space credential](credentials.md) instead of direct membership. Pass the credential as a Bearer token: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...", + { + headers: { + "Authorization": `Bearer ${SPACE_CREDENTIAL}`, + }, + }, +); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...", + { + headers: { + "Authorization": `Bearer ${SPACE_CREDENTIAL}`, + }, + }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("https://happyview.example.com/xrpc/dev.happyview.space.getRecord") + .query(&[("space", "..."), ("collection", "..."), ("rkey", "...")]) + .header("Authorization", format!("Bearer {}", space_credential)) + .send() + .await?; +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...", + nil) +req.Header.Set("Authorization", "Bearer "+spaceCredential) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/dev.happyview.space.getRecord?...' \ -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6InNwYWNlX2NyZWRlbnRpYWwifQ...' ``` diff --git a/packages/docs/content/docs/getting-started/authentication.md b/packages/docs/content/docs/getting-started/authentication.md index affe8c1..b46e894 100644 --- a/packages/docs/content/docs/getting-started/authentication.md +++ b/packages/docs/content/docs/getting-started/authentication.md @@ -36,14 +36,91 @@ Both checks currently log warnings on mismatch rather than rejecting the request ### Calling a query -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_a1b2c3..."; // your API client key + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_a1b2c3..."; // your API client key + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let client_key = "hvc_a1b2c3..."; // your API client key + +let response = client + .get("https://happyview.example.com/xrpc/com.example.feed.getHot") + .header("X-Client-Key", client_key) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_a1b2c3..." // your API client key + +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/com.example.feed.getHot", nil) +req.Header.Set("X-Client-Key", clientKey) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/com.example.feed.getHot' \ -H 'X-Client-Key: hvc_a1b2c3...' ``` For a server-to-server integration, add the secret: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { + headers: { + "X-Client-Key": "hvc_a1b2c3...", + "X-Client-Secret": "hvs_d4e5f6...", + }, + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { + headers: { + "X-Client-Key": "hvc_a1b2c3...", + "X-Client-Secret": "hvs_d4e5f6...", + }, + }, +); +``` +```rust tab="Rust" tab-group="language" +let client_secret = "hvs_d4e5f6..."; // your API client secret + +let response = client + .get("https://happyview.example.com/xrpc/com.example.feed.getHot") + .header("X-Client-Key", client_key) + .header("X-Client-Secret", client_secret) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientSecret := "hvs_d4e5f6..." // your API client secret + +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/com.example.feed.getHot", nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("X-Client-Secret", clientSecret) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/com.example.feed.getHot' \ -H 'X-Client-Key: hvc_a1b2c3...' \ -H 'X-Client-Secret: hvs_d4e5f6...' @@ -96,7 +173,38 @@ Admin endpoints don't use API clients. They require a real HappyView user, ident For automation — CI/CD, monitoring, cron jobs — create an [admin API key](../guides/api-keys.md) at **Settings > API Keys** or via `POST /admin/api-keys` and pass it as a bearer token: -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_your-api-key-here"; + +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + headers: { Authorization: `Bearer ${TOKEN}` }, +}); +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_your-api-key-here"; + +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + headers: { Authorization: `Bearer ${TOKEN}` }, +}); +``` +```rust tab="Rust" tab-group="language" +let token = "hv_your-api-key-here"; + +let response = client + .get("http://127.0.0.1:3000/admin/lexicons") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +token := "hv_your-api-key-here" + +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/lexicons", nil) +req.Header.Set("Authorization", "Bearer "+token) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" export TOKEN="hv_your-api-key-here" curl http://127.0.0.1:3000/admin/lexicons \ -H "Authorization: Bearer $TOKEN" @@ -277,7 +385,75 @@ Response: With a registered session, send XRPC requests using DPoP auth: -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.createPost", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.createPost", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let client_key = "hvc_..."; // your API client key +let access_token = "..."; // DPoP access token +let dpop_proof = "..."; // DPoP proof JWT + +let response = client + .post("https://happyview.example.com/xrpc/com.example.feed.createPost") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ "text": "Hello world" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_..." // your API client key +accessToken := "..." // DPoP access token +dpopProof := "..." // DPoP proof JWT + +body := bytes.NewBufferString(`{"text": "Hello world"}`) + +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/com.example.feed.createPost", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/com.example.feed.createPost' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ diff --git a/packages/docs/content/docs/guides/api-clients.md b/packages/docs/content/docs/guides/api-clients.md index c2901fc..06e24ec 100644 --- a/packages/docs/content/docs/guides/api-clients.md +++ b/packages/docs/content/docs/guides/api-clients.md @@ -51,7 +51,99 @@ Go to **Settings > API Clients > New client** and fill in: ### From the API -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key + +interface ClientResponse { + id: string; + client_key: string; + client_secret?: string; + name: string; + client_id_url: string; + client_uri: string; + redirect_uris: string[]; + client_type: string; + allowed_origins: string[]; +} + +const response = await fetch("http://127.0.0.1:3000/admin/api-clients", { + method: "POST", + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "My atproto Client", + client_id_url: "https://example.com/client-metadata.json", + client_uri: "https://example.com", + redirect_uris: ["https://example.com/oauth/callback"], + client_type: "public", + allowed_origins: ["https://example.com"], + }), +}); + +const client: ClientResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key + +const response = await fetch("http://127.0.0.1:3000/admin/api-clients", { + method: "POST", + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "My atproto Client", + client_id_url: "https://example.com/client-metadata.json", + client_uri: "https://example.com", + redirect_uris: ["https://example.com/oauth/callback"], + client_type: "public", + allowed_origins: ["https://example.com"], + }), +}); + +const client = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let token = "hv_..."; // your API key + +let response = client + .post("http://127.0.0.1:3000/admin/api-clients") + .bearer_auth(token) + .json(&serde_json::json!({ + "name": "My atproto Client", + "client_id_url": "https://example.com/client-metadata.json", + "client_uri": "https://example.com", + "redirect_uris": ["https://example.com/oauth/callback"], + "client_type": "public", + "allowed_origins": ["https://example.com"] + })) + .send() + .await?; + +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key + +body := bytes.NewBufferString(`{ + "name": "My atproto Client", + "client_id_url": "https://example.com/client-metadata.json", + "client_uri": "https://example.com", + "redirect_uris": ["https://example.com/oauth/callback"], + "client_type": "public", + "allowed_origins": ["https://example.com"] +}`) + +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/api-clients", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/api-clients \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ @@ -78,14 +170,95 @@ Every XRPC request must include the client key. HappyView looks for it in this o For public queries that don't need a user identity: -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_a1b2c3..."; // your API client key + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_a1b2c3..."; // your API client key + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let client_key = "hvc_a1b2c3..."; // your API client key + +let response = client + .get("https://happyview.example.com/xrpc/com.example.feed.getHot") + .header("X-Client-Key", client_key) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_a1b2c3..." // your API client key + +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/com.example.feed.getHot", nil) +req.Header.Set("X-Client-Key", clientKey) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/com.example.feed.getHot' \ -H 'X-Client-Key: hvc_a1b2c3...' ``` Server-side callers should also include the secret (since there's no origin to authenticate): -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_SECRET = "hvs_d4e5f6..."; // your API client secret + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { + headers: { + "X-Client-Key": "hvc_a1b2c3...", + "X-Client-Secret": CLIENT_SECRET, + }, + }, +); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_SECRET = "hvs_d4e5f6..."; // your API client secret + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.feed.getHot", + { + headers: { + "X-Client-Key": "hvc_a1b2c3...", + "X-Client-Secret": CLIENT_SECRET, + }, + }, +); +``` +```rust tab="Rust" tab-group="language" +let client_secret = "hvs_d4e5f6..."; // your API client secret + +let response = client + .get("https://happyview.example.com/xrpc/com.example.feed.getHot") + .header("X-Client-Key", client_key) + .header("X-Client-Secret", client_secret) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientSecret := "hvs_d4e5f6..." // your API client secret + +req, _ := http.NewRequest("GET", + "https://happyview.example.com/xrpc/com.example.feed.getHot", nil) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("X-Client-Secret", clientSecret) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl 'https://happyview.example.com/xrpc/com.example.feed.getHot' \ -H 'X-Client-Key: hvc_a1b2c3...' \ -H 'X-Client-Secret: hvs_d4e5f6...' @@ -95,7 +268,75 @@ curl 'https://happyview.example.com/xrpc/com.example.feed.getHot' \ Procedures — and queries whose scripts need to know who the caller is — require a user's OAuth session. This uses [DPoP authentication](../getting-started/authentication.md#dpop-key-provisioning-for-third-party-apps), where each request includes a cryptographic proof that the caller holds the right key. -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.createPost", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.createPost", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let client_key = "hvc_..."; // your API client key +let access_token = "..."; // DPoP access token +let dpop_proof = "..."; // DPoP proof JWT + +let response = client + .post("https://happyview.example.com/xrpc/com.example.createPost") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ "text": "Hello world" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_..." // your API client key +accessToken := "..." // DPoP access token +dpopProof := "..." // DPoP proof JWT + +body := bytes.NewBufferString(`{"text": "Hello world"}`) + +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/com.example.createPost", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/com.example.createPost' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ @@ -244,7 +485,75 @@ Content-Type: application/json With a registered session, sign each request with a DPoP proof: -```sh +```ts tab="TypeScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.createPost", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const CLIENT_KEY = "hvc_..."; // your API client key +const ACCESS_TOKEN = "..."; // DPoP access token +const DPOP_PROOF = "..."; // DPoP proof JWT + +const response = await fetch( + "https://happyview.example.com/xrpc/com.example.createPost", + { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let client_key = "hvc_..."; // your API client key +let access_token = "..."; // DPoP access token +let dpop_proof = "..."; // DPoP proof JWT + +let response = client + .post("https://happyview.example.com/xrpc/com.example.createPost") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ "text": "Hello world" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +clientKey := "hvc_..." // your API client key +accessToken := "..." // DPoP access token +dpopProof := "..." // DPoP proof JWT + +body := bytes.NewBufferString(`{"text": "Hello world"}`) + +req, _ := http.NewRequest("POST", + "https://happyview.example.com/xrpc/com.example.createPost", body) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST 'https://happyview.example.com/xrpc/com.example.createPost' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP ' \ diff --git a/packages/docs/content/docs/guides/api-keys.md b/packages/docs/content/docs/guides/api-keys.md index 427259e..326eb34 100644 --- a/packages/docs/content/docs/guides/api-keys.md +++ b/packages/docs/content/docs/guides/api-keys.md @@ -28,7 +28,53 @@ Each API key has its own set of **scoped permissions**. When you create a key, y Pass the key as a Bearer token in the `Authorization` header: -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"; + +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + headers: { Authorization: `Bearer ${TOKEN}` }, +}); + +interface LexiconsResponse { + lexicons: Array<{ + id: string; + nsid: string; + revision: number; + }>; +} + +const data: LexiconsResponse = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"; + +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + headers: { Authorization: `Bearer ${TOKEN}` }, +}); + +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let token = "hv_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"; + +let response = client + .get("http://127.0.0.1:3000/admin/lexicons") + .bearer_auth(token) + .send() + .await?; + +let data: serde_json::Value = response.json().await?; +``` +```go tab="Go" tab-group="language" +token := "hv_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" + +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/lexicons", nil) +req.Header.Set("Authorization", "Bearer "+token) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl http://127.0.0.1:3000/admin/lexicons \ -H "Authorization: Bearer hv_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" ``` diff --git a/packages/docs/content/docs/guides/event-logs.md b/packages/docs/content/docs/guides/event-logs.md index 363b09a..ba1265c 100644 --- a/packages/docs/content/docs/guides/event-logs.md +++ b/packages/docs/content/docs/guides/event-logs.md @@ -106,7 +106,154 @@ Logged when the WebSocket connection to [Jetstream](https://github.com/bluesky-s Use the admin API to query event logs with filters: -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; + +interface Event { + id: string; + event_type: string; + severity: string; + actor_did?: string; + subject?: string; + detail: Record; + created_at: string; +} + +interface EventsResponse { + events: Event[]; + cursor?: string; +} + +// Get all errors +const errors: EventsResponse = await fetch( + "http://127.0.0.1:3000/admin/events?severity=error", + { headers }, +).then((r) => r.json()); + +// Get script errors for a specific lexicon +const scriptErrors: EventsResponse = await fetch( + "http://127.0.0.1:3000/admin/events?event_type=script.error&subject=com.example.feed.like", + { headers }, +).then((r) => r.json()); + +// Get all lexicon-related events +const lexiconEvents: EventsResponse = await fetch( + "http://127.0.0.1:3000/admin/events?category=lexicon", + { headers }, +).then((r) => r.json()); + +// Paginate through results +const page: EventsResponse = await fetch( + "http://127.0.0.1:3000/admin/events?limit=20&cursor=2026-03-01T11:59:00Z", + { headers }, +).then((r) => r.json()); +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { Authorization: `Bearer ${TOKEN}` }; + +// Get all errors +const errors = await fetch( + "http://127.0.0.1:3000/admin/events?severity=error", + { headers }, +).then((r) => r.json()); + +// Get script errors for a specific lexicon +const scriptErrors = await fetch( + "http://127.0.0.1:3000/admin/events?event_type=script.error&subject=com.example.feed.like", + { headers }, +).then((r) => r.json()); + +// Get all lexicon-related events +const lexiconEvents = await fetch( + "http://127.0.0.1:3000/admin/events?category=lexicon", + { headers }, +).then((r) => r.json()); + +// Paginate through results +const page = await fetch( + "http://127.0.0.1:3000/admin/events?limit=20&cursor=2026-03-01T11:59:00Z", + { headers }, +).then((r) => r.json()); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let token = "hv_..."; // your API key + +// Get all errors +let errors: serde_json::Value = client + .get("http://127.0.0.1:3000/admin/events") + .query(&[("severity", "error")]) + .bearer_auth(token) + .send() + .await? + .json() + .await?; + +// Get script errors for a specific lexicon +let script_errors: serde_json::Value = client + .get("http://127.0.0.1:3000/admin/events") + .query(&[ + ("event_type", "script.error"), + ("subject", "com.example.feed.like"), + ]) + .bearer_auth(token) + .send() + .await? + .json() + .await?; + +// Get all lexicon-related events +let lexicon_events: serde_json::Value = client + .get("http://127.0.0.1:3000/admin/events") + .query(&[("category", "lexicon")]) + .bearer_auth(token) + .send() + .await? + .json() + .await?; + +// Paginate through results +let page: serde_json::Value = client + .get("http://127.0.0.1:3000/admin/events") + .query(&[("limit", "20"), ("cursor", "2026-03-01T11:59:00Z")]) + .bearer_auth(token) + .send() + .await? + .json() + .await?; +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key + +// Get all errors +req, _ := http.NewRequest("GET", + "http://127.0.0.1:3000/admin/events?severity=error", nil) +req.Header.Set("Authorization", "Bearer "+token) +errors, err := http.DefaultClient.Do(req) + +// Get script errors for a specific lexicon +req, _ = http.NewRequest("GET", + "http://127.0.0.1:3000/admin/events?event_type=script.error&subject=com.example.feed.like", nil) +req.Header.Set("Authorization", "Bearer "+token) +scriptErrors, err := http.DefaultClient.Do(req) + +// Get all lexicon-related events +req, _ = http.NewRequest("GET", + "http://127.0.0.1:3000/admin/events?category=lexicon", nil) +req.Header.Set("Authorization", "Bearer "+token) +lexiconEvents, err := http.DefaultClient.Do(req) + +// Paginate through results +req, _ = http.NewRequest("GET", + "http://127.0.0.1:3000/admin/events?limit=20&cursor=2026-03-01T11:59:00Z", nil) +req.Header.Set("Authorization", "Bearer "+token) +page, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" +AUTH="Authorization: Bearer hv_..." # your API key + # Get all errors curl "http://127.0.0.1:3000/admin/events?severity=error" -H "$AUTH" diff --git a/packages/docs/content/docs/guides/labelers.md b/packages/docs/content/docs/guides/labelers.md index 0dcfc59..7610d15 100644 --- a/packages/docs/content/docs/guides/labelers.md +++ b/packages/docs/content/docs/guides/labelers.md @@ -23,9 +23,56 @@ HappyView begins consuming labels from the labeler immediately. The subscription You can also add a labeler via the API: -```sh +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", +}; + +const response = await fetch("http://127.0.0.1:3000/admin/labelers", { + method: "POST", + headers, + body: JSON.stringify({ did: "did:plc:ar7c4by46qjdydhdevvrndac" }), +}); +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key +const headers = { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", +}; + +const response = await fetch("http://127.0.0.1:3000/admin/labelers", { + method: "POST", + headers, + body: JSON.stringify({ did: "did:plc:ar7c4by46qjdydhdevvrndac" }), +}); +``` +```rust tab="Rust" tab-group="language" +let client = reqwest::Client::new(); +let token = "hv_..."; // your API key + +let response = client + .post("http://127.0.0.1:3000/admin/labelers") + .bearer_auth(token) + .json(&serde_json::json!({ "did": "did:plc:ar7c4by46qjdydhdevvrndac" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +token := "hv_..." // your API key +body := bytes.NewBufferString(`{"did": "did:plc:ar7c4by46qjdydhdevvrndac"}`) + +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/labelers", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/labelers \ - -H "$AUTH" \ + -H "Authorization: Bearer hv_..." \ -H "Content-Type: application/json" \ -d '{ "did": "did:plc:ar7c4by46qjdydhdevvrndac" }' ``` @@ -34,9 +81,57 @@ curl -X POST http://127.0.0.1:3000/admin/labelers \ 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 +```ts tab="TypeScript" tab-group="language" +const TOKEN = "hv_..."; // your API key + +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "PATCH", + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "paused" }), + }, +); +``` +```js tab="JavaScript" tab-group="language" +const TOKEN = "hv_..."; // your API key + +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "PATCH", + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "paused" }), + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .patch("http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac") + .bearer_auth(token) + .json(&serde_json::json!({ "status": "paused" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := bytes.NewBufferString(`{"status": "paused"}`) + +req, _ := http.NewRequest("PATCH", + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", body) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X PATCH http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ - -H "$AUTH" \ + -H "Authorization: Bearer hv_..." \ -H "Content-Type: application/json" \ -d '{ "status": "paused" }' ``` @@ -52,9 +147,41 @@ Deleting a labeler removes the subscription **and all labels it has emitted**. T Or via the API: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "DELETE", + headers: { Authorization: `Bearer ${TOKEN}` }, + }, +); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", + { + method: "DELETE", + headers: { Authorization: `Bearer ${TOKEN}` }, + }, +); +``` +```rust tab="Rust" tab-group="language" +let response = client + .delete("http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac") + .bearer_auth(token) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("DELETE", + "http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac", nil) +req.Header.Set("Authorization", "Bearer "+token) + +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X DELETE http://127.0.0.1:3000/admin/labelers/did:plc:ar7c4by46qjdydhdevvrndac \ - -H "$AUTH" + -H "Authorization: Bearer hv_..." ``` ## Labels on records diff --git a/packages/docs/content/docs/reference/script-examples/batch-save.md b/packages/docs/content/docs/reference/script-examples/batch-save.md index 7a6afc3..601168f 100644 --- a/packages/docs/content/docs/reference/script-examples/batch-save.md +++ b/packages/docs/content/docs/reference/script-examples/batch-save.md @@ -31,7 +31,68 @@ end ## Usage -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.batchCreate", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + items: [ + { text: "First", createdAt: "2025-01-01T00:00:00Z" }, + { text: "Second", createdAt: "2025-01-01T00:01:00Z" }, + ], + }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.batchCreate", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + items: [ + { text: "First", createdAt: "2025-01-01T00:00:00Z" }, + { text: "Second", createdAt: "2025-01-01T00:01:00Z" }, + ], + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.batchCreate") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "items": [ + { "text": "First", "createdAt": "2025-01-01T00:00:00Z" }, + { "text": "Second", "createdAt": "2025-01-01T00:01:00Z" } + ] + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ + "items": [ + { "text": "First", "createdAt": "2025-01-01T00:00:00Z" }, + { "text": "Second", "createdAt": "2025-01-01T00:01:00Z" } + ] +}` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.batchCreate", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.batchCreate \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/reference/script-examples/cascading-delete.md b/packages/docs/content/docs/reference/script-examples/cascading-delete.md index 2ce753c..3c4ab9e 100644 --- a/packages/docs/content/docs/reference/script-examples/cascading-delete.md +++ b/packages/docs/content/docs/reference/script-examples/cascading-delete.md @@ -56,7 +56,54 @@ end ## Usage -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.post/abc123", + }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.post/abc123", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "uri": "at://did:plc:abc/xyz.statusphere.post/abc123" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "uri": "at://did:plc:abc/xyz.statusphere.post/abc123" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/reference/script-examples/complex-mutations.md b/packages/docs/content/docs/reference/script-examples/complex-mutations.md index f21a529..c96de21 100644 --- a/packages/docs/content/docs/reference/script-examples/complex-mutations.md +++ b/packages/docs/content/docs/reference/script-examples/complex-mutations.md @@ -68,7 +68,64 @@ end ## Usage -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.updatePost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.post/abc123", + tags: ["tutorial", "atproto"], + title: " My Post Title ", + }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.updatePost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.post/abc123", + tags: ["tutorial", "atproto"], + title: " My Post Title ", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.updatePost") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "uri": "at://did:plc:abc/xyz.statusphere.post/abc123", + "tags": ["tutorial", "atproto"], + "title": " My Post Title " + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ + "uri": "at://did:plc:abc/xyz.statusphere.post/abc123", + "tags": ["tutorial", "atproto"], + "title": " My Post Title " +}` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.updatePost", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.updatePost \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/reference/script-examples/create-record.md b/packages/docs/content/docs/reference/script-examples/create-record.md index 5939226..9dd901e 100644 --- a/packages/docs/content/docs/reference/script-examples/create-record.md +++ b/packages/docs/content/docs/reference/script-examples/create-record.md @@ -22,7 +22,57 @@ end ## Usage -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.createRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text: "Hello world", + createdAt: "2025-01-01T00:00:00Z", + }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.createRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text: "Hello world", + createdAt: "2025-01-01T00:00:00Z", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.createRecord") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "text": "Hello world", + "createdAt": "2025-01-01T00:00:00Z" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "text": "Hello world", "createdAt": "2025-01-01T00:00:00Z" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.createRecord", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.createRecord \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/reference/script-examples/sidecar-records.md b/packages/docs/content/docs/reference/script-examples/sidecar-records.md index 326d8b9..6dc2c3e 100644 --- a/packages/docs/content/docs/reference/script-examples/sidecar-records.md +++ b/packages/docs/content/docs/reference/script-examples/sidecar-records.md @@ -41,7 +41,60 @@ end ## Usage -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.createPost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text: "Hello world", + lang: "en", + source: "web", + }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.createPost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text: "Hello world", + lang: "en", + source: "web", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.createPost") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "text": "Hello world", + "lang": "en", + "source": "web" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "text": "Hello world", "lang": "en", "source": "web" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.createPost", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.createPost \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/reference/script-examples/signed-record-verify.md b/packages/docs/content/docs/reference/script-examples/signed-record-verify.md index 9ac99bf..8b3989c 100644 --- a/packages/docs/content/docs/reference/script-examples/signed-record-verify.md +++ b/packages/docs/content/docs/reference/script-examples/signed-record-verify.md @@ -34,7 +34,33 @@ end ## Usage -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.example.getPost?uri=at://did:plc:abc/xyz.example.post/3abc123&did=did:plc:abc", +); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.example.getPost?uri=at://did:plc:abc/xyz.example.post/3abc123&did=did:plc:abc", +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/xrpc/xyz.example.getPost") + .query(&[ + ("uri", "at://did:plc:abc/xyz.example.post/3abc123"), + ("did", "did:plc:abc"), + ]) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/xrpc/xyz.example.getPost?uri=at://did:plc:abc/xyz.example.post/3abc123&did=did:plc:abc", nil) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl "http://127.0.0.1:3000/xrpc/xyz.example.getPost?uri=at://did:plc:abc/xyz.example.post/3abc123&did=did:plc:abc" ``` diff --git a/packages/docs/content/docs/reference/script-examples/signed-record.md b/packages/docs/content/docs/reference/script-examples/signed-record.md index 144c43c..62b0f64 100644 --- a/packages/docs/content/docs/reference/script-examples/signed-record.md +++ b/packages/docs/content/docs/reference/script-examples/signed-record.md @@ -31,7 +31,48 @@ end ## Usage -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.example.createPost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.example.createPost", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: "Hello world" }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.example.createPost") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ "text": "Hello world" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "text": "Hello world" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.example.createPost", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.example.createPost \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/reference/script-examples/update-or-delete.md b/packages/docs/content/docs/reference/script-examples/update-or-delete.md index 4a2882d..adaa59a 100644 --- a/packages/docs/content/docs/reference/script-examples/update-or-delete.md +++ b/packages/docs/content/docs/reference/script-examples/update-or-delete.md @@ -38,22 +38,170 @@ end ## Usage -```sh -# Create +**Create:** + +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "hello" }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "hello" }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ "status": "hello" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "status": "hello" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "hello" }' +``` + +**Update:** -# Update +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.record/abc123", + status: "updated", + }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.record/abc123", + status: "updated", + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "uri": "at://did:plc:abc/xyz.statusphere.record/abc123", + "status": "updated" + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "uri": "at://did:plc:abc/xyz.statusphere.record/abc123", "status": "updated" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "uri": "at://did:plc:abc/xyz.statusphere.record/abc123", "status": "updated" }' +``` + +**Delete:** -# Delete +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.record/abc123", + delete: true, + }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uri: "at://did:plc:abc/xyz.statusphere.record/abc123", + delete: true, + }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "uri": "at://did:plc:abc/xyz.statusphere.record/abc123", + "delete": true + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "uri": "at://did:plc:abc/xyz.statusphere.record/abc123", "delete": true }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setRecord \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/reference/script-examples/upsert-record.md b/packages/docs/content/docs/reference/script-examples/upsert-record.md index 1774302..74180ad 100644 --- a/packages/docs/content/docs/reference/script-examples/upsert-record.md +++ b/packages/docs/content/docs/reference/script-examples/upsert-record.md @@ -41,16 +41,110 @@ end ## Usage -```sh -# Create: no rkey, so a new TID is generated +**Create** (no rkey, so a new TID is generated): + +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "hello" }), +}); +const data = await response.json(); +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "hello" }), +}); +const data = await response.json(); +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ "status": "hello" })) + .send() + .await?; +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```go tab="Go" tab-group="language" +body := `{ "status": "hello" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "hello" }' # → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` -# Update: pass the rkey back to update the same record +**Update** (pass the rkey back to update the same record): + +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ rkey: "3abc123", status: "updated" }), +}); +const data = await response.json(); +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ rkey: "3abc123", status: "updated" }), +}); +const data = await response.json(); +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus") + .header("X-Client-Key", client_key) + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ "rkey": "3abc123", "status": "updated" })) + .send() + .await?; +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```go tab="Go" tab-group="language" +body := `{ "rkey": "3abc123", "status": "updated" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +// → { "uri": "at://did:plc:abc/xyz.statusphere.status/3abc123", "cid": "bafyrei..." } +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ diff --git a/packages/docs/content/docs/sdk/lex-agent/changelog.md b/packages/docs/content/docs/sdk/lex-agent/changelog.md index a43b984..570948e 100644 --- a/packages/docs/content/docs/sdk/lex-agent/changelog.md +++ b/packages/docs/content/docs/sdk/lex-agent/changelog.md @@ -1,5 +1,5 @@ --- -title: "Changelog" +title: "@happyview/lex-agent" --- diff --git a/packages/docs/content/docs/sdk/oauth-client-browser/changelog.md b/packages/docs/content/docs/sdk/oauth-client-browser/changelog.md index d25a298..9e3a2d4 100644 --- a/packages/docs/content/docs/sdk/oauth-client-browser/changelog.md +++ b/packages/docs/content/docs/sdk/oauth-client-browser/changelog.md @@ -1,5 +1,5 @@ --- -title: "Changelog" +title: "@happyview/oauth-client-browser" --- diff --git a/packages/docs/content/docs/sdk/oauth-client-node/changelog.md b/packages/docs/content/docs/sdk/oauth-client-node/changelog.md index ad569ea..3acda40 100644 --- a/packages/docs/content/docs/sdk/oauth-client-node/changelog.md +++ b/packages/docs/content/docs/sdk/oauth-client-node/changelog.md @@ -1,5 +1,5 @@ --- -title: "Changelog" +title: "@happyview/oauth-client-node" --- diff --git a/packages/docs/content/docs/sdk/oauth-client/changelog.md b/packages/docs/content/docs/sdk/oauth-client/changelog.md index 9f3c6cd..1b1694b 100644 --- a/packages/docs/content/docs/sdk/oauth-client/changelog.md +++ b/packages/docs/content/docs/sdk/oauth-client/changelog.md @@ -1,5 +1,5 @@ --- -title: "Changelog" +title: "@happyview/oauth-client" --- diff --git a/packages/docs/content/docs/tutorials/statusphere.md b/packages/docs/content/docs/tutorials/statusphere.md index 5a22434..3316153 100644 --- a/packages/docs/content/docs/tutorials/statusphere.md +++ b/packages/docs/content/docs/tutorials/statusphere.md @@ -36,7 +36,122 @@ HappyView now subscribes to `xyz.statusphere.status` via Jetstream and kicks off You can also add lexicons via the [admin API](../api-reference/admin/lexicons.md). This is useful for automation or CI/CD workflows: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + method: "POST", + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + lexicon_json: { + lexicon: 1, + id: "xyz.statusphere.status", + defs: { + main: { + type: "record", + key: "tid", + record: { + type: "object", + required: ["status", "createdAt"], + properties: { + status: { type: "string", maxGraphemes: 1 }, + createdAt: { type: "string", format: "datetime" }, + }, + }, + }, + }, + }, + backfill: true, + }), +}); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/admin/lexicons", { + method: "POST", + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + lexicon_json: { + lexicon: 1, + id: "xyz.statusphere.status", + defs: { + main: { + type: "record", + key: "tid", + record: { + type: "object", + required: ["status", "createdAt"], + properties: { + status: { type: "string", maxGraphemes: 1 }, + createdAt: { type: "string", format: "datetime" }, + }, + }, + }, + }, + }, + backfill: true, + }), +}); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/admin/lexicons") + .header("Authorization", format!("Bearer {}", token)) + .json(&serde_json::json!({ + "lexicon_json": { + "lexicon": 1, + "id": "xyz.statusphere.status", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": ["status", "createdAt"], + "properties": { + "status": { "type": "string", "maxGraphemes": 1 }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + } + } + }, + "backfill": true + })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ + "lexicon_json": { + "lexicon": 1, + "id": "xyz.statusphere.status", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": ["status", "createdAt"], + "properties": { + "status": { "type": "string", "maxGraphemes": 1 }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + } + } + }, + "backfill": true +}` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/lexicons", bytes.NewBufferString(body)) +req.Header.Set("Authorization", "Bearer "+token) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/admin/lexicons \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ @@ -135,7 +250,34 @@ The `collection` variable at the top tells the script which record collection to Try it out: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=5", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=5", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses") + .query(&[("limit", "5")]) + .header("X-Client-Key", client_key) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=5", nil) +req.Header.Set("X-Client-Key", clientKey) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=5" \ -H "X-Client-Key: $CLIENT_KEY" ``` @@ -160,14 +302,68 @@ curl "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?limit=5" \ Filter by a specific user: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?did=did:plc:abc&limit=1", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?did=did:plc:abc&limit=1", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses") + .query(&[("did", "did:plc:abc"), ("limit", "1")]) + .header("X-Client-Key", client_key) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?did=did:plc:abc&limit=1", nil) +req.Header.Set("X-Client-Key", clientKey) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?did=did:plc:abc&limit=1" \ -H "X-Client-Key: $CLIENT_KEY" ``` Fetch a single record by URI: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?uri=at://did:plc:abc/xyz.statusphere.status/3abc123", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch( + "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?uri=at://did:plc:abc/xyz.statusphere.status/3abc123", + { headers: { "X-Client-Key": CLIENT_KEY } }, +); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .get("http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses") + .query(&[("uri", "at://did:plc:abc/xyz.statusphere.status/3abc123")]) + .header("X-Client-Key", client_key) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?uri=at://did:plc:abc/xyz.statusphere.status/3abc123", nil) +req.Header.Set("X-Client-Key", clientKey) +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl "http://127.0.0.1:3000/xrpc/xyz.statusphere.listStatuses?uri=at://did:plc:abc/xyz.statusphere.status/3abc123" \ -H "X-Client-Key: $CLIENT_KEY" ``` @@ -214,7 +410,52 @@ This creates a `POST /xrpc/xyz.statusphere.setStatus` endpoint that creates reco Set a status. This requires DPoP authentication — the [JavaScript SDK](../sdk/overview.md) handles this for you, but you can test with curl if you have a token: -```sh +```ts tab="TypeScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "🚀" }), +}); +const data = await response.json(); +``` +```js tab="JavaScript" tab-group="language" +const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", { + method: "POST", + headers: { + "X-Client-Key": CLIENT_KEY, + Authorization: `DPoP ${ACCESS_TOKEN}`, + DPoP: DPOP_PROOF, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status: "🚀" }), +}); +const data = await response.json(); +``` +```rust tab="Rust" tab-group="language" +let response = client + .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus") + .header("X-Client-Key", client_key) + .header("Authorization", format!("DPoP {}", access_token)) + .header("DPoP", dpop_proof) + .json(&serde_json::json!({ "status": "🚀" })) + .send() + .await?; +``` +```go tab="Go" tab-group="language" +body := `{ "status": "🚀" }` +req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus", bytes.NewBufferString(body)) +req.Header.Set("X-Client-Key", clientKey) +req.Header.Set("Authorization", "DPoP "+accessToken) +req.Header.Set("DPoP", dpopProof) +req.Header.Set("Content-Type", "application/json") +resp, err := http.DefaultClient.Do(req) +``` +```sh tab="cURL" tab-group="language" curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.setStatus \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: DPoP $TOKEN" \ diff --git a/packages/docs/source.config.ts b/packages/docs/source.config.ts index 26b5fd5..5bef3f8 100644 --- a/packages/docs/source.config.ts +++ b/packages/docs/source.config.ts @@ -1,5 +1,5 @@ import { defineDocs, defineConfig } from 'fumadocs-mdx/config'; -import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'; +import { remarkMdxMermaid, remarkCodeTab } from 'fumadocs-core/mdx-plugins'; const neonLagoonDark = { name: 'neon-lagoon-dark', @@ -109,7 +109,7 @@ export const docs = defineDocs({ export default defineConfig({ mdxOptions: { - remarkPlugins: [remarkMdxMermaid], + remarkPlugins: [remarkMdxMermaid, remarkCodeTab], rehypeCodeOptions: { themes: { light: neonLagoonLight, -- 2.51.2