Changeset 0.28.0.3 (#175)
This commit is contained in:
@@ -101,22 +101,70 @@ Standard HTTP status codes: 400 (bad request), 401 (not authenticated),
|
||||
403 (not authorized), 404 (not found), 409 (conflict), 500 (server error),
|
||||
502 (upstream provider failure).
|
||||
|
||||
### Pagination Envelope
|
||||
### Response Envelopes
|
||||
|
||||
Endpoints that paginate return:
|
||||
Three patterns. Every endpoint uses exactly one.
|
||||
|
||||
**List (returns an array)** → always wrap in `{"data": []}`:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [...]
|
||||
}
|
||||
```
|
||||
|
||||
If paginated, pagination fields sit alongside `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"page": 1,
|
||||
"per_page": 50,
|
||||
"total": 142,
|
||||
"total_pages": 3
|
||||
"total": 142
|
||||
}
|
||||
```
|
||||
|
||||
Query params: `?page=1&per_page=50`. Not all list endpoints paginate —
|
||||
some return `{ "data": [...] }` or a bare array in a named key.
|
||||
unpaginated lists still use `{"data": [...]}`.
|
||||
|
||||
This is a hard rule: `data` is the only array wrapper key. No
|
||||
domain-specific keys (`teams`, `members`, `configs`, etc.) for arrays.
|
||||
Clients parse every list response identically.
|
||||
|
||||
**Single object (GET by ID, profile, health)** → return the object directly:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "...",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
No wrapping. The response *is* the resource.
|
||||
|
||||
**Composite (multiple named values, not an array)** → named keys:
|
||||
|
||||
```json
|
||||
{
|
||||
"active": 5,
|
||||
"pending": 2
|
||||
}
|
||||
```
|
||||
|
||||
Used for stats, counts, status endpoints — anything returning
|
||||
multiple named scalars or heterogeneous data. The key names are
|
||||
domain-specific and documented per endpoint.
|
||||
|
||||
**Empty arrays** must serialize as `[]`, never `null`. Go handlers
|
||||
must guard nil slices before serialization:
|
||||
|
||||
```go
|
||||
if result == nil {
|
||||
result = []MyType{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
```
|
||||
|
||||
### ID Format
|
||||
|
||||
|
||||
@@ -3,35 +3,58 @@
|
||||
### My Teams
|
||||
|
||||
```
|
||||
GET /teams/mine → { "teams": [...] }
|
||||
GET /teams/mine → { "data": [...] }
|
||||
```
|
||||
|
||||
Returns teams the current user is a member of.
|
||||
**Auth:** Authenticated user.
|
||||
|
||||
Returns teams the current user is a member of (active teams only).
|
||||
|
||||
Each team object:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Engineering",
|
||||
"description": "...",
|
||||
"is_active": true,
|
||||
"settings": "{}",
|
||||
"my_role": "admin|member",
|
||||
"member_count": 5
|
||||
}
|
||||
```
|
||||
|
||||
### Team Administration
|
||||
|
||||
Team-admin-scoped routes (require `RequireTeamAdmin` middleware):
|
||||
|
||||
**Team CRUD (platform admin):**
|
||||
|
||||
```
|
||||
GET /admin/teams → { "teams": [...] }
|
||||
POST /admin/teams
|
||||
GET /admin/teams/:id
|
||||
PUT /admin/teams/:id
|
||||
GET /admin/teams → { "data": [...], "total": N, "page": N, "per_page": N }
|
||||
POST /admin/teams ← { "name", "description" } → { "id", "name" }
|
||||
GET /admin/teams/:id → team object (unwrapped)
|
||||
PUT /admin/teams/:id ← { "name"?, "description"?, "is_active"?, "settings"? }
|
||||
DELETE /admin/teams/:id
|
||||
```
|
||||
|
||||
**Members:**
|
||||
**Auth:** `RequireAdmin` (platform admin).
|
||||
|
||||
`POST` returns `201`. `PUT`/`DELETE` return `{"ok": true}`. `PUT` with
|
||||
no fields returns `400`. Duplicate name returns `409`.
|
||||
|
||||
**Members (admin routes):**
|
||||
|
||||
```
|
||||
GET /admin/teams/:id/members → { "members": [...] }
|
||||
POST /admin/teams/:id/members ← { "user_id", "role" }
|
||||
PUT /admin/teams/:id/members/:memberId ← { "role" }
|
||||
GET /admin/teams/:id/members → { "data": [...] }
|
||||
POST /admin/teams/:id/members ← { "user_id", "role" } → { "id" }
|
||||
PUT /admin/teams/:id/members/:memberId ← { "role" }
|
||||
DELETE /admin/teams/:id/members/:memberId
|
||||
```
|
||||
|
||||
**Team-scoped routes** (team admin, not platform admin):
|
||||
**Auth:** `RequireAdmin`.
|
||||
|
||||
`role` must be `admin` or `member`. Duplicate membership returns `409`.
|
||||
|
||||
**Team-scoped routes** (team admin self-service):
|
||||
|
||||
```
|
||||
GET /teams/:teamId/members
|
||||
@@ -40,47 +63,88 @@ PUT /teams/:teamId/members/:memberId
|
||||
DELETE /teams/:teamId/members/:memberId
|
||||
```
|
||||
|
||||
**Auth:** `RequireTeamAdmin`.
|
||||
|
||||
Same handlers as admin routes; the `getTeamID()` helper reads either
|
||||
`:id` or `:teamId`.
|
||||
|
||||
**Team Providers:**
|
||||
|
||||
```
|
||||
GET /teams/:teamId/providers → { "configs": [...] }
|
||||
POST /teams/:teamId/providers ← { "name", "provider", "endpoint", "api_key", ... }
|
||||
GET /teams/:teamId/providers → { "data": [...], "allow_team_providers": bool }
|
||||
POST /teams/:teamId/providers ← { "name", "provider", "endpoint", "api_key", ... }
|
||||
PUT /teams/:teamId/providers/:id
|
||||
DELETE /teams/:teamId/providers/:id
|
||||
GET /teams/:teamId/providers/:id/models → { "models": [...] }
|
||||
GET /teams/:teamId/providers/:id/models → { "models": [...], "provider": "name" }
|
||||
```
|
||||
|
||||
**Auth:** `RequireTeamAdmin`.
|
||||
|
||||
`GET` returns provider configs scoped to the team. The `models` endpoint
|
||||
performs a live query to the upstream provider.
|
||||
|
||||
**Team Models:**
|
||||
|
||||
```
|
||||
GET /teams/:teamId/models → { "models": [...] }
|
||||
```
|
||||
|
||||
Available models for this team (global + team-scoped).
|
||||
**Auth:** `RequireTeamAdmin`.
|
||||
|
||||
**Team Personas:** See §4.2.
|
||||
Returns all models available to this team: global admin models with
|
||||
`visibility IN ('enabled', 'team')` plus live-queried team provider
|
||||
models with `source` field (`"global"` or `"team"`).
|
||||
|
||||
**Team Groups:**
|
||||
|
||||
```
|
||||
GET /teams/:teamId/groups → { "data": [...] }
|
||||
```
|
||||
|
||||
**Auth:** `RequireTeamAdmin`.
|
||||
|
||||
Returns groups scoped to this team.
|
||||
|
||||
**Team Personas:** See personas.md §Team Personas.
|
||||
|
||||
**Team Roles:**
|
||||
|
||||
```
|
||||
GET /teams/:teamId/roles → { "roles": [...] }
|
||||
PUT /teams/:teamId/roles/:role ← { "permissions": {...} }
|
||||
GET /teams/:teamId/roles → { "data": {...} }
|
||||
PUT /teams/:teamId/roles/:role ← RoleConfig object
|
||||
DELETE /teams/:teamId/roles/:role
|
||||
```
|
||||
|
||||
**Auth:** `RequireTeamAdmin`.
|
||||
|
||||
`GET` returns the team's `model_roles` settings map (composite object,
|
||||
not an array). Initially empty `{}`. Each key is a role name, value is
|
||||
a `RoleConfig`. `PUT` validates the role name. `DELETE` removes the
|
||||
override, falling back to the global role definition.
|
||||
|
||||
**Team Audit:**
|
||||
|
||||
```
|
||||
GET /teams/:teamId/audit → paginated audit log
|
||||
GET /teams/:teamId/audit/actions → { "actions": [...] } (distinct action types)
|
||||
GET /teams/:teamId/audit → { "data": [...], "total": N, "page": N, "per_page": N }
|
||||
GET /teams/:teamId/audit/actions → { "actions": [...] }
|
||||
```
|
||||
|
||||
**Auth:** `RequireTeamAdmin`.
|
||||
|
||||
Audit log is scoped to actions performed by team members. Supports
|
||||
query filters: `?action=`, `?actor_id=`, `?resource_type=`.
|
||||
|
||||
**Team Usage:**
|
||||
|
||||
```
|
||||
GET /teams/:teamId/usage → { "totals": {...}, "results": [...] }
|
||||
```
|
||||
|
||||
**Auth:** `RequireTeamAdmin`.
|
||||
|
||||
Returns usage against team-owned providers. Supports query params for
|
||||
date range filtering.
|
||||
|
||||
### Groups & Resource Grants
|
||||
|
||||
Groups are ACL containers that decouple access from team membership.
|
||||
@@ -90,45 +154,58 @@ scope.
|
||||
**My Groups:**
|
||||
|
||||
```
|
||||
GET /groups/mine → { "groups": [...] }
|
||||
GET /groups/mine → { "data": [...] }
|
||||
```
|
||||
|
||||
**Auth:** Authenticated user.
|
||||
|
||||
**Admin Group CRUD:**
|
||||
|
||||
```
|
||||
GET /admin/groups → { "groups": [...] }
|
||||
POST /admin/groups ← { "name", "scope", "team_id" }
|
||||
GET /admin/groups/:id
|
||||
PUT /admin/groups/:id
|
||||
GET /admin/groups → { "data": [...] }
|
||||
POST /admin/groups ← { "name", "scope", "team_id"? }
|
||||
GET /admin/groups/:id → group object (unwrapped)
|
||||
PUT /admin/groups/:id ← { "name"?, "description"?, "permissions"?, ... }
|
||||
DELETE /admin/groups/:id
|
||||
```
|
||||
|
||||
**Auth:** `RequireAdmin`.
|
||||
|
||||
`POST` returns `201`. Duplicate name returns `409`. Deleting a
|
||||
system-sourced group (e.g. Everyone) returns `400`.
|
||||
|
||||
**Group Members:**
|
||||
|
||||
```
|
||||
GET /admin/groups/:id/members → { "members": [...] }
|
||||
GET /admin/groups/:id/members → { "data": [...] }
|
||||
POST /admin/groups/:id/members ← { "user_id" }
|
||||
DELETE /admin/groups/:id/members/:userId
|
||||
```
|
||||
|
||||
**Auth:** `RequireAdmin`.
|
||||
|
||||
**Resource Grants:**
|
||||
|
||||
```
|
||||
GET /admin/grants/:type/:id → { "grant": {...} }
|
||||
PUT /admin/grants/:type/:id ← { "grant_type": "team_only|global|groups", "group_ids": [...] }
|
||||
GET /admin/grants/:type/:id → grant object (unwrapped)
|
||||
PUT /admin/grants/:type/:id ← { "grant_scope", "granted_groups"?: [...] }
|
||||
DELETE /admin/grants/:type/:id
|
||||
```
|
||||
|
||||
**Auth:** `RequireAdmin`.
|
||||
|
||||
`:type` is `persona` or `kb`. `:id` is the resource ID.
|
||||
|
||||
Grant types:
|
||||
Grant scopes:
|
||||
|
||||
| `grant_type` | Visibility |
|
||||
|--------------|-----------|
|
||||
| `grant_scope` | Visibility |
|
||||
|---------------|-----------|
|
||||
| `team_only` | Only the owning team |
|
||||
| `global` | All authenticated users |
|
||||
| `groups` | Members of specified groups |
|
||||
|
||||
Setting `grant_scope: "groups"` without `granted_groups` returns `400`.
|
||||
|
||||
### Permissions
|
||||
|
||||
Groups carry fine-grained permissions. Permission resolution:
|
||||
@@ -140,13 +217,18 @@ union of all group permissions + Everyone group permissions.
|
||||
GET /admin/permissions → { "permissions": ["model.use", "kb.read", ...] }
|
||||
```
|
||||
|
||||
**Auth:** `RequireAdmin`.
|
||||
|
||||
**Get user's effective permissions:**
|
||||
|
||||
```
|
||||
GET /admin/users/:id/permissions → { "permissions": [...], "groups": [...] }
|
||||
GET /admin/users/:id/permissions → { "permissions": [...], "user_id": "...", "groups": [...] }
|
||||
```
|
||||
|
||||
Returns the resolved permission set and contributing groups.
|
||||
**Auth:** `RequireAdmin`.
|
||||
|
||||
Returns the resolved permission set and contributing group IDs
|
||||
(always includes the Everyone group).
|
||||
|
||||
**Group permission fields** (set via `PUT /admin/groups/:id`):
|
||||
|
||||
@@ -174,4 +256,3 @@ row needed). Editable by admins, cannot be deleted (`source=system`).
|
||||
resolved permission set. Returns 403 if the required permission is not present.
|
||||
|
||||
See [enums.md](enums.md) for the full permission constant list.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user