# 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 `). 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). ---