Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ All notable changes to `doc` are documented here.
(case-insensitive). New `after` / `before` (ISO 8601) filter by `updatedAt`; `sort` accepts
`updated_desc` (default), `updated_asc`, `created_desc`, `created_asc`. Shared query parser
(`src/lib/doc-query.ts`) keeps the two routes consistent; invalid dates/sort return 400.
- Document host-app integration guide for search, time range, and sort params (#73).

- Adoption of the shared `@fullstack-ai-infra/ui` design system for the first document
workflow: workspace shell (responsive sidebar with compact mode), editor chrome, and
Expand Down
43 changes: 42 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,54 @@ The response contains `authenticated`, `userId`, and the token's `scopes`.
### List documents

```http
GET /api/v1/documents?limit=50&cursor=...&query=...&starred=true&trash=false
GET /api/v1/documents?limit=50&cursor=...&query=...&starred=true&trash=false&after=...&before=...&sort=...
```

The list contains documents owned by the token's user. `limit` defaults to `50` and must be from
`1` to `100`. Follow `meta.nextCursor` until it is `null`; cursors are opaque. `starred` and
`trash` accept only `true` or `false`. `query` is limited to 200 characters.

#### Search

`query` searches both document **title** and **content** (case-insensitive). A document matches
when either field contains the keyword. The search uses `OR` semantics — a match in the title,
the body, or both all return the document.

#### Time range filters

- `after` — return documents updated strictly after this ISO 8601 date (e.g. `2026-09-01` or
`2026-09-01T00:00:00.000Z`).
- `before` — return documents updated strictly before this ISO 8601 date.

Both are optional and can be combined. Invalid dates return `400 invalid_query`.

#### Sort

`sort` controls result ordering. Accepted values:

| Value | Order |
| -------------- | ------------------------------------- |
| `updated_desc` | Most recently updated first (default) |
| `updated_asc` | Least recently updated first |
| `created_desc` | Newest created first |
| `created_asc` | Oldest created first |

Invalid values return `400 invalid_query`. Cursor pagination (`cursor` param) is only compatible
with `updated_*` sort orders; combining `cursor` with `created_*` returns `400 invalid_cursor`.

#### Examples

```http
# Search content and title for "runbook", updated in September 2026
GET /api/v1/documents?query=runbook&after=2026-09-01&before=2026-09-30

# Oldest documents first
GET /api/v1/documents?sort=created_asc

# Recently updated documents matching "deployment"
GET /api/v1/documents?query=deployment&sort=updated_desc
```

```json
{
"data": [
Expand Down
18 changes: 9 additions & 9 deletions docs/CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ Primary implementation paths:

## Identity, governance, and product surface

| Capability | Status | Current implementation |
| ---------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ |
| Authentication | available | GitHub OAuth, email sign-in, and scoped personal access tokens with expiry, revocation, and one-time secret display |
| Document API v1 | available | Bearer-only token inspection, owner listing, authorized reads, canonical creation, and ETag-guarded metadata updates |
| Document authorization | experimental | Browser routes, API v1, publication, and collaboration entry points enforce persisted ownership and READ/WRITE relations |
| User settings | available | User name and avatar |
| Admin governance | available | Overview, user/admin management, document filtering, restore/delete, and publication moderation |
| Localization and theme | available | Chinese/English plus dark, light, and system themes |
| Object storage | experimental | Uploads use the current Ali OSS client; a provider-neutral S3 interface is not implemented |
| Capability | Status | Current implementation |
| ---------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authentication | available | GitHub OAuth, email sign-in, and scoped personal access tokens with expiry, revocation, and one-time secret display |
| Document API v1 | available | Bearer-only token inspection, owner listing with content search, time range filters, sort options, authorized reads, canonical creation, and ETag-guarded metadata updates |
| Document authorization | experimental | Browser routes, API v1, publication, and collaboration entry points enforce persisted ownership and READ/WRITE relations |
| User settings | available | User name and avatar |
| Admin governance | available | Overview, user/admin management, document filtering, restore/delete, and publication moderation |
| Localization and theme | available | Chinese/English plus dark, light, and system themes |
| Object storage | experimental | Uploads use the current Ali OSS client; a provider-neutral S3 interface is not implemented |

Primary implementation paths:

Expand Down
139 changes: 139 additions & 0 deletions docs/host-app-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Host app integration guide

Host applications that embed `doc` as a read-only knowledge source (e.g. RoleWeave Memory &
Collaboration panel) consume documents through the API v1 list endpoint. This guide describes
how to use the search, time range, and sort parameters effectively.

## Base endpoint

```http
GET /api/v1/documents
Authorization: Bearer doc_pat_...
```

A token with `documents:read` scope is required. See [API.md](API.md) for authentication details.

## Search across title and content

The `query` parameter searches both document title and body content with OR semantics. A document
is returned if the keyword appears in either field.

```http
GET /api/v1/documents?query=deployment%20procedure
```

`query` is a **single substring**, not a list of OR terms. `deployment%20procedure` (a space)
matches documents whose title or body contains the 21-character string `deployment procedure`.
It does not match a document that has `deployment` in the title and `procedure` only in the
body as two separate words. `+` in the query string is a space, not a boolean operator.

The search is case-insensitive and uses substring matching (`ILIKE`-style). For host apps that
need to show where the match was found, there is currently no `matchField` indicator in the API
response — the host app should fetch individual documents (`GET /api/v1/documents/{id}`) and
inspect the content if highlight positioning is needed.

## Filter by time range

Use `after` and `before` to narrow results to a specific update window. Both filter on
`updatedAt`.

```http
# Documents updated in the last 7 days
GET /api/v1/documents?after=2026-09-11T00:00:00.000Z

# Documents updated in September 2026
GET /api/v1/documents?after=2026-09-01&before=2026-09-30
```

Dates accept any ISO 8601 format. Invalid dates return `400 invalid_query`.

## Sort results

Control ordering with the `sort` parameter:

```http
# Most recently created first
GET /api/v1/documents?sort=created_desc

# Least recently updated first
GET /api/v1/documents?sort=updated_asc
```

Default is `updated_desc`. When using cursor-based pagination (`cursor` param), only `updated_*`
sort orders are supported — `created_*` sorts with a cursor return `400 invalid_cursor`.

## Pagination

The endpoint uses cursor-based pagination. Always follow `meta.nextCursor` until it is `null`:

```
Page 1: GET /api/v1/documents?limit=50&query=runbook
→ meta.nextCursor = "eyJ1cGRhdGVkQXQi..."

Page 2: GET /api/v1/documents?limit=50&query=runbook&cursor=eyJ1cGRhdGVkQXQi...
→ meta.nextCursor = null (done)
```

Cursor pagination is tied to `(updatedAt, id)`. If you need `created_*` ordering, you must fetch
all results (no cursor) and sort client-side, or use offset-based logic with `after`/`before`.

## Combining parameters

All parameters can be combined:

```http
GET /api/v1/documents?query=onboarding&after=2026-08-01&sort=created_desc&limit=20
```

This returns documents matching "onboarding" in title or content, updated after August 1 2026,
ordered by creation date descending, 20 per page.

## Internal API (browser session)

The internal `GET /api/doc` endpoint used by the doc web UI also supports the same search and
filter parameters, using `keyword` instead of `query`:

```http
GET /api/doc?keyword=runbook&after=2026-09-01&sort=updated_desc
```

This endpoint uses the browser session cookie for authentication and is not intended for host app
integration — use API v1 with a PAT instead.

## Error handling

| Status | Code | Meaning |
| ------ | -------------------- | --------------------------------------------- |
| 400 | `invalid_query` | Invalid `after`/`before` date or `sort` value |
| 400 | `invalid_cursor` | Cursor used with `created_*` sort |
| 401 | `unauthorized` | Missing or invalid PAT |
| 403 | `insufficient_scope` | Token lacks `documents:read` |

## Testing the date range filter

Host apps should verify that `after` and `before` filter on `updatedAt`, not `createdAt`. A
document edited today but created last month will appear in an `after=last-week` query.

```typescript
const res = await fetch(
`${DOC_ORIGIN}/api/v1/documents?after=2026-09-11T00:00:00.000Z&before=2026-09-18T00:00:00.000Z`,
{ headers: { Authorization: `Bearer ${token}` } }
)

const { data, requestId } = await res.json()

// Every returned document must have updatedAt within the range
for (const doc of data) {
const updated = new Date(doc.updatedAt)
console.assert(updated > new Date('2026-09-11T00:00:00.000Z'), `${doc.id} updatedAt out of range`)
console.assert(updated < new Date('2026-09-18T00:00:00.000Z'), `${doc.id} updatedAt out of range`)
}

// Invalid date returns 400
const bad = await fetch(`${DOC_ORIGIN}/api/v1/documents?after=not-a-date`, {
headers: { Authorization: `Bearer ${token}` },
})
console.assert(bad.status === 400)
const body = await bad.json()
console.assert(body.error.code === 'invalid_query')
```
Loading