272 lines
6.3 KiB
Markdown
272 lines
6.3 KiB
Markdown
# Projects
|
|
|
|
**Projects** are organizational containers that group channels,
|
|
knowledge bases, notes, and files. They follow the scope model
|
|
(personal, team, global).
|
|
|
|
All endpoints require authentication (`Authorization: Bearer <token>`).
|
|
Admin endpoints additionally require `role=admin`.
|
|
|
|
## Project Object
|
|
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"name": "Q3 Research",
|
|
"description": "...",
|
|
"color": "#3b82f6",
|
|
"icon": "folder",
|
|
"scope": "personal|team|global",
|
|
"owner_id": "uuid",
|
|
"team_id": "uuid|null",
|
|
"workspace_id": "uuid|null",
|
|
"is_archived": false,
|
|
"settings": {},
|
|
"channel_count": 5,
|
|
"kb_count": 2,
|
|
"note_count": 3,
|
|
"created_at": "2025-01-15T10:30:00Z",
|
|
"updated_at": "2025-01-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| `id` | uuid | PK, auto-generated |
|
|
| `name` | string | Required, max 200 chars |
|
|
| `description` | string | Optional |
|
|
| `color` | string? | CSS hex color, max 7 chars |
|
|
| `icon` | string? | Icon identifier, max 50 chars |
|
|
| `scope` | enum | `personal`, `team`, `global` |
|
|
| `owner_id` | uuid | Set from JWT, immutable |
|
|
| `team_id` | uuid? | Set for team-scoped projects |
|
|
| `workspace_id` | uuid? | Linked workspace |
|
|
| `is_archived` | bool | Default `false` |
|
|
| `settings` | object | JSONB, default `{}` |
|
|
| `channel_count` | int | Computed, omitted when 0 |
|
|
| `kb_count` | int | Computed, omitted when 0 |
|
|
| `note_count` | int | Computed, omitted when 0 |
|
|
| `created_at` | datetime | Auto-set |
|
|
| `updated_at` | datetime | Auto-updated on change |
|
|
|
|
## Project CRUD
|
|
|
|
### List Projects
|
|
|
|
```
|
|
GET /projects → { "data": [...] }
|
|
```
|
|
|
|
Query params: `?include_archived=true` to include archived projects.
|
|
|
|
Returns projects the user can access: personal (own), team (member),
|
|
and global. Ordered by name.
|
|
|
|
### Create Project
|
|
|
|
```
|
|
POST /projects → 201, project object
|
|
```
|
|
|
|
```json
|
|
{
|
|
"name": "My Project",
|
|
"description": "Optional description",
|
|
"color": "#3b82f6",
|
|
"icon": "folder"
|
|
}
|
|
```
|
|
|
|
| Field | Required | Notes |
|
|
|-------|----------|-------|
|
|
| `name` | yes | Max 200 chars |
|
|
| `description` | no | |
|
|
| `color` | no | CSS hex |
|
|
| `icon` | no | Icon identifier |
|
|
|
|
Scope is set to `personal` and `owner_id` is taken from the JWT.
|
|
Returns the created project object (bare, no envelope).
|
|
|
|
### Get Project
|
|
|
|
```
|
|
GET /projects/:id → project object
|
|
```
|
|
|
|
Returns bare project object (no envelope). 404 if not found or
|
|
not accessible.
|
|
|
|
### Update Project
|
|
|
|
```
|
|
PUT /projects/:id → updated project object
|
|
```
|
|
|
|
Partial update — only supplied fields are changed.
|
|
|
|
```json
|
|
{
|
|
"name": "New Name",
|
|
"description": "Updated",
|
|
"color": "#ef4444",
|
|
"icon": "star",
|
|
"is_archived": true,
|
|
"workspace_id": "uuid|null",
|
|
"settings": { "key": "value" }
|
|
}
|
|
```
|
|
|
|
Settings are merged (overlay, not replace). Returns the refreshed
|
|
project object after update.
|
|
|
|
### Delete Project
|
|
|
|
```
|
|
DELETE /projects/:id → { "message": "project deleted" }
|
|
```
|
|
|
|
Only the project owner or an admin can delete. Channels get
|
|
`project_id` set to NULL; junction rows cascade.
|
|
|
|
---
|
|
|
|
## Channel Association
|
|
|
|
```
|
|
GET /projects/:id/channels → { "data": [...] }
|
|
POST /projects/:id/channels ← { "channel_id", "position" }
|
|
DELETE /projects/:id/channels/:channelId → { "message": "channel removed" }
|
|
PUT /projects/:id/channels/reorder ← { "channel_ids": ["uuid1", "uuid2"] }
|
|
```
|
|
|
|
A channel can only belong to one project. `POST` performs an atomic
|
|
move if the channel is already in a different project. The user must
|
|
own the channel being added.
|
|
|
|
**Channel association object:**
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"channel_id": "uuid",
|
|
"position": 0,
|
|
"folder": "",
|
|
"added_at": "2025-01-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
**POST request:**
|
|
|
|
| Field | Required | Notes |
|
|
|-------|----------|-------|
|
|
| `channel_id` | yes | UUID of channel to add |
|
|
| `position` | no | Sort order (default 0) |
|
|
|
|
---
|
|
|
|
## KB Association
|
|
|
|
```
|
|
GET /projects/:id/knowledge-bases → { "data": [...] }
|
|
POST /projects/:id/knowledge-bases ← { "kb_id", "auto_search" }
|
|
DELETE /projects/:id/knowledge-bases/:kbId → { "message": "KB removed" }
|
|
```
|
|
|
|
KBs bound to a project are automatically available to every channel
|
|
in that project (resolution chain injection at completion time).
|
|
Upsert on conflict — re-posting updates `auto_search`.
|
|
|
|
**KB association object:**
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"kb_id": "uuid",
|
|
"auto_search": true,
|
|
"added_at": "2025-01-15T10:30:00Z",
|
|
"name": "KB Name"
|
|
}
|
|
```
|
|
|
|
`name` is enriched via JOIN from `knowledge_bases.name`.
|
|
|
|
**POST request:**
|
|
|
|
| Field | Required | Notes |
|
|
|-------|----------|-------|
|
|
| `kb_id` | yes | UUID of knowledge base |
|
|
| `auto_search` | no | Default `false` |
|
|
|
|
---
|
|
|
|
## Note Association
|
|
|
|
```
|
|
GET /projects/:id/notes → { "data": [...] }
|
|
POST /projects/:id/notes ← { "note_id" }
|
|
DELETE /projects/:id/notes/:noteId → { "message": "note removed" }
|
|
```
|
|
|
|
**Note association object:**
|
|
|
|
```json
|
|
{
|
|
"project_id": "uuid",
|
|
"note_id": "uuid",
|
|
"added_at": "2025-01-15T10:30:00Z",
|
|
"title": "Note Title"
|
|
}
|
|
```
|
|
|
|
`title` is enriched via JOIN from `notes.title`.
|
|
Insert uses ON CONFLICT DO NOTHING (idempotent).
|
|
|
|
---
|
|
|
|
## Project Files
|
|
|
|
```
|
|
GET /projects/:id/files → { "files": [...], "count": N }
|
|
POST /projects/:id/files ← multipart/form-data
|
|
```
|
|
|
|
Project-level file uploads (distinct from workspace files and channel
|
|
attachments). Note: `GET` uses `"files"` key, not `"data"`.
|
|
|
|
---
|
|
|
|
## Admin Project Management
|
|
|
|
```
|
|
GET /admin/projects → { "data": [...] }
|
|
DELETE /admin/projects/:id → { "message": "project deleted" }
|
|
```
|
|
|
|
Query params: `?include_archived=true`.
|
|
|
|
Cross-instance visibility for platform admins. Admin list returns
|
|
an enriched object with extra fields:
|
|
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"name": "...",
|
|
"description": "...",
|
|
"scope": "personal",
|
|
"owner_id": "uuid",
|
|
"team_id": null,
|
|
"is_archived": false,
|
|
"created_at": "...",
|
|
"updated_at": "...",
|
|
"channel_count": 5,
|
|
"kb_count": 2,
|
|
"note_count": 3,
|
|
"owner_name": "jdoe"
|
|
}
|
|
```
|
|
|
|
Note: admin list object omits `color`, `icon`, `workspace_id`,
|
|
and `settings` (uses a local struct, not the full model).
|
|
|
|
---
|