This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/ICD/projects.md
gobha 96a4f16bc5 Changeset 0.37.17 (#229)
Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
2026-03-24 16:50:00 +00:00

7.8 KiB

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

{
  "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
{
  "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.

{
  "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:

{
  "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:

{
  "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:

{
  "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 (v0.37.17 — workspace-backed)

Project files are stored in the project's workspace (auto-created on first upload). The response uses "files" key, not "data". Files are WorkspaceFile objects with tree structure (directories + files).

List Files

GET  /projects/:id/files                         → { "files": [...], "count": N }
     ?path=/docs&recursive=true
Param Default Notes
path "" Directory to list (empty = root)
recursive true Include subdirectories

Upload File

POST /projects/:id/files                         ← multipart/form-data
     ?path=/docs

Multipart form field: file. Optional path query param to upload into a subdirectory. Auto-creates the project workspace on first upload. Returns 201 with { path, filename, content_type, size_bytes }.

Errors: 413 if file exceeds size limit or workspace quota.

Download File

GET  /projects/:id/files/download?path=/docs/readme.md

Streams raw file content with Content-Disposition: attachment.

Delete File

DELETE /projects/:id/files?path=/docs/readme.md
       &recursive=false

recursive=true to delete directories with contents.

Create Directory

POST /projects/:id/files/mkdir                   ← { "path": "/docs/images" }

Path also accepted via ?path= query param. Returns 201.

Upload Archive

POST /projects/:id/archive/upload                ← multipart/form-data

Multipart field: file. Accepts .zip, .tar.gz, .tgz. Extracts contents into the workspace root. Returns { ok: true, files_extracted: N }.

Download Archive

GET  /projects/:id/archive/download?format=zip

Bundles all project files into a zip (or tar.gz). Streams with Content-Disposition: attachment.


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:

{
  "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).