From 1dfdbc12fe707aea598bfdea5b7024e9af7da4ce Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Fri, 20 Mar 2026 21:24:17 +0100 Subject: [PATCH] docs: add public sharing to features, architecture, and API docs - FEATURES.md: add public link sharing feature details - ARCHITECTURE.md: add shared_notes table to schema section - openapi.yaml: add share create/revoke, public note, and public attachment endpoints with SharedNoteData schema - Regenerate API.md Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/API.md | 109 ++++++++++++++++++++++++++++++++++++++ docs/ARCHITECTURE.md | 1 + docs/FEATURES.md | 11 +++- docs/openapi.yaml | 121 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 240 insertions(+), 2 deletions(-) diff --git a/docs/API.md b/docs/API.md index 48dc70b..1be2a0b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -410,6 +410,115 @@ Owner can remove anyone. Collaborator can only remove self (leave). Use `userId= --- +## Sharing + +### `POST /api/notes/{id}/share` + +Create a public share link + +Owner-only. Creates a public share URL for the note. Idempotent — returns existing token if already shared. + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `id` | path | string | yes | | + +**Response:** `201` + +```json +{ + "token": "token", + "url": "url" +} +``` + +**Errors:** `404` Note not found or not owner + +--- + +### `DELETE /api/notes/{id}/share` + +Revoke a public share link + +Owner-only. Removes the public share link for the note. + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `id` | path | string | yes | | + +**Response:** `204` + +**Errors:** `404` Note not found or not owner + +--- + +### `GET /api/shared/{token}` + +Get a shared note (public) + +No authentication required. Returns the note content for a valid share token. Rate limited to 60 requests per minute per IP. + +> No authentication required. + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `token` | path | string | yes | | + +**Response:** `200` + +```json +{ + "title": "title", + "content": "content", + "checklistMode": true, + "color": "default", + "attachments": [ + { + "id": "id", + "noteId": "noteId", + "filename": "filename", + "mimeType": "mimeType", + "size": 1, + "thumbnailPath": "thumbnailPath", + "createdAt": "2025-01-01T00:00:00.000Z" + } + ], + "createdAt": "2025-01-01T00:00:00.000Z", + "updatedAt": "2025-01-01T00:00:00.000Z" +} +``` + +**Errors:** `404` Invalid token or note is trashed, `429` Rate limit exceeded + +--- + +### `GET /api/shared/{token}/attachment/{attachmentId}` + +Get an attachment from a shared note (public) + +No authentication required. Serves image binary for an attachment belonging to the shared note. Rate limited. + +> No authentication required. + +**Parameters:** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `token` | path | string | yes | | +| `attachmentId` | path | string | yes | | +| `thumb` | query | string | no | Set to '1' to get the thumbnail version (1) | + +**Response:** `200` + +**Errors:** `404` Invalid token, note trashed, or attachment not found, `429` Rate limit exceeded + +--- + ## Users ### `GET /api/users/search` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e43c343..d824fe4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -100,6 +100,7 @@ Key tables: - `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 +- `shared_notes` - Public share links (noteId → token, 1:1 with cascade delete) - `sync_log` - Sync operation history ### Collaboration Access Control diff --git a/docs/FEATURES.md b/docs/FEATURES.md index ac4466f..a656292 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -81,13 +81,20 @@ - Permanently delete from trash ### Note Sharing / Collaboration +- **Public link sharing**: share any note via a public URL — no login required for viewers + - Toggle public link on/off from the share dialog + - Copy shareable URL with one click + - Read-only public page with full markdown/checklist/image rendering + - Public links are automatically invalidated when a note is trashed + - Rate-limited public endpoints (60 req/min per IP) + - Globe icon on note cards indicates a public link is active - 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 +- Sharing indicator on note cards (Globe for public, Users for collaborators-only) +- Hover popover showing sharing status and collaborator names - Share dialog accessible from editor toolbar - Collaborators can leave shared notes - Shared notes appear mixed in main list alongside owned notes diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 27df53e..88ddbf1 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -237,6 +237,104 @@ paths: schema: $ref: '#/components/schemas/Success' + /api/notes/{id}/share: + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + post: + summary: Create a public share link + description: Owner-only. Creates a public share URL for the note. Idempotent — returns existing token if already shared. + tags: [Sharing] + responses: + '201': + description: Share link created + content: + application/json: + schema: + type: object + properties: + token: + type: string + url: + type: string + format: uri + '404': + description: Note not found or not owner + delete: + summary: Revoke a public share link + description: Owner-only. Removes the public share link for the note. + tags: [Sharing] + responses: + '204': + description: Share link revoked + '404': + description: Note not found or not owner + + /api/shared/{token}: + parameters: + - name: token + in: path + required: true + schema: + type: string + get: + summary: Get a shared note (public) + description: No authentication required. Returns the note content for a valid share token. Rate limited to 60 requests per minute per IP. + tags: [Sharing] + security: [] + responses: + '200': + description: Shared note data + content: + application/json: + schema: + $ref: '#/components/schemas/SharedNoteData' + '404': + description: Invalid token or note is trashed + '429': + description: Rate limit exceeded + + /api/shared/{token}/attachment/{attachmentId}: + parameters: + - name: token + in: path + required: true + schema: + type: string + - name: attachmentId + in: path + required: true + schema: + type: string + get: + summary: Get an attachment from a shared note (public) + description: No authentication required. Serves image binary for an attachment belonging to the shared note. Rate limited. + tags: [Sharing] + security: [] + parameters: + - name: thumb + in: query + schema: + type: string + enum: ['1'] + description: Set to '1' to get the thumbnail version + responses: + '200': + description: Image binary + content: + image/*: + schema: + type: string + format: binary + '404': + description: Invalid token, note trashed, or attachment not found + '429': + description: Rate limit exceeded + /api/users/search: get: summary: Search users @@ -520,6 +618,29 @@ components: description: API key (crumbs_...) schemas: + SharedNoteData: + type: object + properties: + title: + type: string + content: + type: string + checklistMode: + type: boolean + color: + type: string + enum: [default, coral, peach, sand, mint, sage, fog, storm, dusk, blossom, clay, chalk] + attachments: + type: array + items: + $ref: '#/components/schemas/Attachment' + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + Success: type: object properties: