diff --git a/docs/API.md b/docs/API.md index f37fbfe..48dc70b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -321,6 +321,123 @@ Permanently delete a note --- +## Collaboration + +### `GET /api/notes/{id}/collaborators` + +List collaborators + +Returns all collaborators for a note. Requires access (owner or collaborator). + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `id` | path | string | yes | | + +**Response:** `200` + +```json +[ + { + "userId": 1, + "displayName": "displayName", + "email": "email", + "addedAt": "2025-01-01T00:00:00.000Z" + } +] +``` + +--- + +### `POST /api/notes/{id}/collaborators` + +Add a collaborator + +Owner-only. Adds a user as collaborator on the note. + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `id` | path | string | yes | | + +**Request:** + +```json +{ + "userId": 1 +} +``` + +**Response:** `201` + +```json +[ + { + "userId": 1, + "displayName": "displayName", + "email": "email", + "addedAt": "2025-01-01T00:00:00.000Z" + } +] +``` + +**Errors:** `400` Invalid request (missing userId, owner as collaborator), `409` User is already a collaborator + +--- + +### `DELETE /api/notes/{id}/collaborators` + +Remove a collaborator + +Owner can remove anyone. Collaborator can only remove self (leave). Use `userId=self` to leave. + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `id` | path | string | yes | | +| `userId` | query | | yes | | + +**Response:** `200` + +```json +{ + "success": true +} +``` + +--- + +## Users + +### `GET /api/users/search` + +Search users + +Search registered users by email or display name. Excludes the current user. Any authenticated user can access. + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `q` | query | string | yes | Search query (matches email or displayName) | + +**Response:** `200` + +```json +[ + { + "id": 1, + "displayName": "displayName", + "email": "email" + } +] +``` + +--- + ## Attachments ### `GET /api/notes/{id}/attachments` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ec6fa50..e43c343 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -98,8 +98,21 @@ Key tables: - `note_tags` - Many-to-many note ↔ tag - `attachments` - Image file metadata (path + thumbnailPath) - `api_keys` - MCP API keys (SHA-256 hashed, prefix for display) +- `note_collaborators` - Sharing relationships (noteId + userId + addedBy) +- `note_user_state` - Per-user pin/archive/sortOrder for shared notes - `sync_log` - Sync operation history +### Collaboration Access Control + +Notes support sharing with other users on the same instance: + +- **Owner**: Full control (edit, trash, delete, share/unshare) +- **Collaborator**: Can edit content, color, checklist mode; has independent pin/archive/sortOrder state via `note_user_state` +- Access checks use `canAccessNote()` / `requireNoteAccess()` / `requireNoteOwnership()` helpers +- `note_user_state` stores per-user view state (pinned, archived, sortOrder) — collaborators see their own organization +- When owner trashes a note, it vanishes for all collaborators +- Collaborators can leave (remove themselves) from shared notes + ## MCP Server Crumbs includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) server that allows AI assistants (Claude Code, Claude Desktop, etc.) to interact with notes programmatically. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index c8dced9..41e1cf3 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -80,6 +80,18 @@ - Restore notes from trash - Permanently delete from trash +### Note Sharing / Collaboration +- Share notes with other users on the same instance +- User search autocomplete for adding collaborators +- Full collaboration: both owner and collaborators can edit content, color, and checklist mode +- Per-user state: pin, archive, and sort order are independent per user +- Owner-only actions: trash, delete, share/unshare +- Collaborator indicator (Users icon) on shared note cards +- Hover popover showing collaborator names +- Share dialog accessible from editor toolbar +- Collaborators can leave shared notes +- Shared notes appear mixed in main list alongside owned notes + ### Full-Text Search - Search across note titles, content, and tags - Real-time search results as you type diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 2167f35..27df53e 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -168,6 +168,104 @@ paths: '404': description: Note not found + /api/notes/{id}/collaborators: + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + get: + summary: List collaborators + description: Returns all collaborators for a note. Requires access (owner or collaborator). + tags: [Collaboration] + responses: + '200': + description: Collaborator list + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Collaborator' + post: + summary: Add a collaborator + description: Owner-only. Adds a user as collaborator on the note. + tags: [Collaboration] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [userId] + properties: + userId: + type: integer + responses: + '201': + description: Collaborator added, returns updated list + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Collaborator' + '400': + description: Invalid request (missing userId, owner as collaborator) + '409': + description: User is already a collaborator + delete: + summary: Remove a collaborator + description: Owner can remove anyone. Collaborator can only remove self (leave). Use `userId=self` to leave. + tags: [Collaboration] + parameters: + - name: userId + in: query + required: true + schema: + oneOf: + - type: integer + - type: string + enum: [self] + responses: + '200': + description: Collaborator removed + content: + application/json: + schema: + $ref: '#/components/schemas/Success' + + /api/users/search: + get: + summary: Search users + description: Search registered users by email or display name. Excludes the current user. Any authenticated user can access. + tags: [Users] + parameters: + - name: q + in: query + required: true + schema: + type: string + description: Search query (matches email or displayName) + responses: + '200': + description: Matching users (max 10) + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + displayName: + type: string + email: + type: string + /api/notes/{id}/attachments: parameters: - name: id @@ -542,6 +640,19 @@ components: data: $ref: '#/components/schemas/NoteInput' + Collaborator: + type: object + properties: + userId: + type: integer + displayName: + type: string + email: + type: string + addedAt: + type: string + format: date-time + ApiKey: type: object properties: