## How Message Shares and Forwards Work

This document explains how **Message Share Links** and **Forwards** work in the CV API.

It is written for both **humans** and **AI agents** that need to:

- **Create** public share links or forwards
- **Read** shared messages and their attachments/audio
- **Update** who can access an existing share
- **Revoke** access to shares
- **List** all shares for one or many messages

All examples below assume the base path:

- `message-sharelinks` (from `MESSAGE_SHARE_LINK_BASE_PATH`)

---

### Core Concepts

- **Shared message (`shared_message_id`)**
  - The original message being exposed via a share/forward.
  - Stored on the share as `shared_message_id`.
- **Forwarded/related message (`message_id`)**
  - When a message is **forwarded**, the target message that will show the “forward bubble”.
  - Only used when `share_type = "forward"`.
  - Must be a message the logged-in user **created**.
- **Share Link (`share_link_id`)**
  - The identifier of the share/forward itself (backed by `MessageForward` schema).
  - Exposed as `:share_link_id` in endpoints like:
    - `GET /message-sharelinks/:share_link_id`
    - `PUT /message-sharelinks/:share_link_id/access`
- **ShareType (`share_type`)**
  - `forward` – create a **forward** inside a channel/message.
  - `link` – create a **share-by-link** (public or specified access).
- **AccessType (`access_type`)**
  - `public` – anyone can open the share link (subject to expiry/revocation).
  - `forward` – access linked to the underlying message/channel permissions.
  - `specified` – only a set of users/channels/workspaces can open it.
- **SpecifiedAccessType (`specified_access[].type`)**
  - `user` – whitelist specific users (by id).
  - `channel` – whitelist members of specific channels.
  - `workspace` / `workspace_group` – whitelist users by workspace membership.

Internally, both **forwards** and **share links** are stored in the same model; behavior is driven by the combination of `share_type` and `access_type`.

---

### Authentication and Base Path

- **Authenticated endpoints** (require `@TokenAuth()` and `@CurrentUser`):
  - `GET /message-sharelinks/shared-links/:message_id`
  - `GET /message-sharelinks/:share_link_id` (with logged user)
  - `POST /message-sharelinks`
  - `PUT /message-sharelinks/revoke-access`
  - `PUT /message-sharelinks/:share_link_id/access`
  - `POST /message-sharelinks/by-message-ids`
- **Public endpoints** (decorated with `@Public()`):
  - `GET /message-sharelinks/:share_link_id`
  - `GET /message-sharelinks/:share_link_id/attachments/signedurl/:attachment_id`
  - `GET /message-sharelinks/:share_link_id/:audio_id`

AI agents should:

- **Always include** the user token for non-`@Public()` endpoints.
- **Never assume** access: respect `403` / custom error codes from the API.

---

## 1. Creating Shares and Forwards

**Endpoint:** `POST /message-sharelinks`  
**Body type:** `CreateMessageShareLink`

Fields:

- `**shared_message_id`** (`string`, required)
  - The message that is being shared/forwarded.
- `**share_type`** (`"link" | "forward"`, required)
- `**message_id`** (`string`, required only when `share_type = "forward"`)
  - The message that will display the forward “bubble”.
- `**access_type`** (`"public" | "specified" | "forward"`, required)
- `**specified_access**` (`SpecifiedAccess[]`, required when `access_type = "specified"`)
- `**end_access_at**` (`Date`, optional)
  - When the share link should expire.

### 1.1 Create a Public Share Link (anyone with link)

This creates a **public share-by-link** for a message.

**Request**

```json
POST /message-sharelinks
Authorization: Bearer <token>
Content-Type: application/json

{
  "shared_message_id": "65f1234567890abcdef0001",
  "share_type": "link",
  "access_type": "public",
}
```

**Key rules**

- `message_id` is **not** required for `share_type = "link"`.
- Anyone with the generated `share_link_id` can open it until `end_access_at` (or revocation).

**Response (shape example)**

```json
{
  "_id": "share_1234567890abcdef",
  "shared_message_id": "65f1234567890abcdef0001",
  "share_type": "link",
  "access_type": "public",
  "created_by": "user_abc",
  "created_at": "2026-03-10T10:00:00.000Z"
}
```

### 1.2 Create a Forward (share_type = forward)

This creates a **forward** of a message into another message/channel.

**Request**

```json
POST /message-sharelinks
Authorization: Bearer <token>
Content-Type: application/json

{
  "shared_message_id": "65f1234567890abcdef0001",
  "share_type": "forward",
  "message_id": "65f1234567890abcdef0999",
  "access_type": "forward"
}
```

**Validation rules (service-level)**:

- When `share_type = "forward"`:
  - `message_id` **must** be present.
  - `access_type` **must** be `"forward"`.
  - `specified_access` **must not** be present.
  - `message_id` must be a message created by the logged-in user.
- If the user has already forwarded the same (`message_id`, `shared_message_id`) combination, the API returns:
  - `400` with `code = "MESSAGE_ALREADY_BEEN_SHARED"`.

### 1.3 Create a Link with Specified Access

This creates a **share-by-link** that is only visible to a limited audience.

**Request**

```json
POST /message-sharelinks
Authorization: Bearer <token>
Content-Type: application/json

{
  "shared_message_id": "65f1234567890abcdef0001",
  "share_type": "link",
  "access_type": "specified",
  "specified_access": [
    {
      "type": "user",
      "ids": ["user_111", "user_222"]
    },
    {
      "type": "channel",
      "ids": ["channel_abc"]
    }
  ],
  "end_access_at": "2026-04-01T00:00:00.000Z"
}
```

**Validation rules**

- When `access_type = "specified"`:
  - `specified_access` **must** be a non-empty array.
- When `share_type = "link"`:
  - `access_type` **cannot** be `"forward"`.

---

## 2. Reading Shares and Shared Content

### 2.1 Get a Share Link and Shared Message

There are two versions of the main **read** endpoint:

- **v2** (default): returns `MessageShareLinkWithSharedMessage` (rewritten URLs, limited info).
- **v3**: returns `MessageShareLinkWithMessage` with more raw message data and `has_channel_access`.

**v2 – Get Share Link with Shared Message**

```http
GET /message-sharelinks/:share_link_id
Authorization: Bearer <token>   # optional for public shares
```

**Example response (simplified)**

```json
{
  "_id": "share_1234567890abcdef",
  "share_type": "link",
  "access_type": "public",
  "shared_message": {
    "message_id": "65f1234567890abcdef0001",
    "channel_ids": ["channel_abc"],
    "workspace_ids": ["workspace_xyz"],
    "text": "Hello world",
    "audio_models": [
      {
        "_id": "audio_1",
        "url": "https://api.carbonvoice.app/message-sharelinks/share_1234567890abcdef/audio_1"
      }
    ],
    "attachments": [
      {
        "_id": "att_1",
        "type": "file",
        "link": "https://api.carbonvoice.app/message-sharelinks/share_1234567890abcdef/attachments/signedurl/att_1"
      }
    ]
  }
}
```

**v3 – Get Share Link with Full Message**

```http
GET /message-sharelinks/:share_link_id
Accept: application/json;version=3
Authorization: Bearer <token>
```

**Example response (simplified)**

```json
{
  "_id": "share_1234567890abcdef",
  "share_type": "link",
  "access_type": "public",
  "shared_message": {
    "message_id": "65f1234567890abcdef0001",
    "channel_ids": ["channel_abc"],
    "workspace_ids": ["workspace_xyz"],
    "text": "Hello world",
    "audio_models": [
      {
        "_id": "audio_1",
        "url": "https://api.carbonvoice.app/message-sharelinks/share_1234567890abcdef/audio_1"
      }
    ],
    "attachments": [
      {
        "_id": "att_1",
        "type": "file",
        "link": "https://api.carbonvoice.app/message-sharelinks/share_1234567890abcdef/attachments/signedurl/att_1"
      }
    ]
  },
  "has_channel_access": true
}
```

### 2.2 Get All Share Links for a Shared Message

**Endpoint:** `GET /message-sharelinks/shared-links/:message_id`  
**Body type:** `GetMessageShareLinksBySharedMessageFilter` (via query)

```http
GET /message-sharelinks/shared-links/65f1234567890abcdef0001
Authorization: Bearer <token>
```

**Example response (simplified)**

```json
{
  "filters": {
    "page": 1,
    "limit": 20
  },
  "total_results": 2,
  "results": [
    {
      "_id": "share_public_link",
      "shared_message_id": "65f1234567890abcdef0001",
      "share_type": "link",
      "access_type": "public",
      "end_access_at": "2026-04-01T00:00:00.000Z"
    },
    {
      "_id": "share_forward_channel",
      "shared_message_id": "65f1234567890abcdef0001",
      "share_type": "forward",
      "access_type": "forward",
      "channel_id": "channel_abc"
    }
  ]
}
```

The service applies visibility rules so the caller only sees forwards they’re allowed to know about.

### 2.3 Download Attachments and Audio from a Share

- **Attachment signed URL**
  ```http
  GET /message-sharelinks/:share_link_id/attachments/signedurl/:attachment_id
  ```
  **Example (public share, no auth header required)**
  ```http
  GET /message-sharelinks/share_1234567890abcdef/attachments/signedurl/att_1
  ```
  **Example response**
  ```json
  "https://s3.amazonaws.com/bucket/file-name?X-Amz-SignedHeaders=..."
  ```
- **Audio file**
  ```http
  GET /message-sharelinks/:share_link_id/:audio_id
  ```
  This returns a **302 redirect** to a signed S3 URL.
  ```http
  GET /message-sharelinks/share_1234567890abcdef/audio_1
  ```

---

## 3. Revoking Access to Shares

**Endpoint:** `PUT /message-sharelinks/revoke-access`  
**Body type:** `RevokeAccessToShareLink`

You can:

- Revoke **one specific** share link (`share_link_id`), or
- Revoke **all** share links that point to a given `shared_message_id`.

Exactly **one** of `share_link_id` or `shared_message_id` must be provided.

### 3.1 Revoke a Single Share Link

**Request**

```json
PUT /message-sharelinks/revoke-access
Authorization: Bearer <token>
Content-Type: application/json

{
  "share_link_id": "share_1234567890abcdef"
}
```

**Permissions**

- Allowed if:
  - Logged user is the **creator** of the share link; or
  - Logged user is the **creator of the original shared message**.

On success, the API returns `204 No Content`.

### 3.2 Revoke All Share Links for a Shared Message

**Request**

```json
PUT /message-sharelinks/revoke-access
Authorization: Bearer <token>
Content-Type: application/json

{
  "shared_message_id": "65f1234567890abcdef0001"
}
```

This revokes **all** shares (both forwards and links) that reference this `shared_message_id`.  
The service also notifies clients and updates related messages’ `last_updated_at` where applicable.

---

## 4. Updating Access for an Existing Share Link

**Endpoint:** `PUT /message-sharelinks/:share_link_id/access`  
**Body type:** `UpdateAccessPayload`

This only works when:

- `share_type = "link"` for the target share.
- Logged user is the **creator** of the share link.

`UpdateAccessPayload` fields:

- `**access_type`** (`"public" | "specified"`)
- `**specified_access`** (`SpecifiedAccess[]`, optional; required when `access_type = "specified"`)
- `**end_access_at`** (`Date`, optional)

### 4.1 Make a Share Link Public

**Request**

```json
PUT /message-sharelinks/share_1234567890abcdef/access
Authorization: Bearer <token>
Content-Type: application/json

{
  "access_type": "public",
  "end_access_at": "2026-05-01T00:00:00.000Z"
}
```

**Effect**

- Sets `access_type` to `"public"` and updates the expiration date.

### 4.2 Restrict a Share Link to Specific Users

**Request**

```json
PUT /message-sharelinks/share_1234567890abcdef/access
Authorization: Bearer <token>
Content-Type: application/json

{
  "access_type": "specified",
  "specified_access": [
    {
      "type": "user",
      "ids": ["user_111", "user_222"]
    }
  ],
  "end_access_at": "2026-05-01T00:00:00.000Z"
}
```

If `access_type = "specified"` and `specified_access` is empty, the service throws `400 Bad Request`.

---

## 5. Listing Share Links by Message IDs (Creator View)

**Endpoint:** `POST /message-sharelinks/by-message-ids`  
**Body type:** `GetListOfSharelinksByMessageIds`

This endpoint lets the **creator of messages** see, for many messages at once:

- Whether each message is public-shared.
- Which share links exist per message.

**Request body**

- `**message_ids`** (`string[]`, required, 1–500)
- `**start_date`** (`Date`, optional)
- `**end_date`** (`Date`, optional, must be after `start_date`, with a max allowed range)

### 5.1 Example: Get Shares for Two Messages

**Request**

```json
POST /message-sharelinks/by-message-ids
Authorization: Bearer <token>
Content-Type: application/json

{
  "message_ids": [
    "65f1234567890abcdef0001",
    "65f1234567890abcdef0002"
  ],
  "start_date": "2026-03-01T00:00:00.000Z",
  "end_date": "2026-03-31T23:59:59.000Z"
}
```

**Example response**

```json
[
  {
    "message_id": "65f1234567890abcdef0001",
    "is_public_shared": true,
    "sharelinks": [
      {
        "_id": "share_public_link_1",
        "shared_message_id": "65f1234567890abcdef0001",
        "share_type": "link",
        "access_type": "public",
        "end_access_at": "2026-04-01T00:00:00.000Z"
      }
    ]
  },
  {
    "message_id": "65f1234567890abcdef0002",
    "is_public_shared": false,
    "sharelinks": []
  }
]
```

Notes:

- Only messages where the logged user is the **creator** are considered when returning share links.
- Messages for which the user is not the creator will appear with `sharelinks: []`.

---

## 6. Behavioral Rules for AI Agents

- **Choosing `share_type`**
  - Use `share_type = "link"` for **share-by-link** flows (public or specified).
  - Use `share_type = "forward"` when creating a **forward bubble** attached to another message.
- **Valid `share_type` + `access_type` combinations**
  - When `share_type = "forward"`:
    - `access_type` **must** be `"forward"`.
    - `message_id` **must** be provided.
    - `specified_access` **must not** be provided.
  - When `share_type = "link"`:
    - `access_type` can be `"public"` or `"specified"`.
    - `specified_access` is required if `access_type = "specified"`.
- **Revocation behavior**
  - Prefer **targeted revocation** (`share_link_id`) if you know the exact share to revoke.
  - Use **bulk revocation** (`shared_message_id`) to remove all access to a message in one go.
- **Checking if a message has any public share**
  - The service exposes `messageHasPublicAccessShareLink(messageId)` internally.
  - At the HTTP layer, use:
    - `POST /message-sharelinks/by-message-ids` and inspect `is_public_shared`.
- **Error handling**
  - Treat `403` with codes like:
    - `SHARED_MESSAGE_ACCESS_REVOKED`
    - `SHARED_MESSAGE_EXPIRED`
    - `FORWARDED_MESSAGE_ACCESS_REVOKED`
    - `FORWARDED_MESSAGE_EXPIRED`
  - As hard denials: do not auto-retry with the same user context.

---

## 7. Quick Endpoint Reference

- **Create share / forward**
  - `POST /message-sharelinks`
- **Read share (v2 / v3)**
  - `GET /message-sharelinks/:share_link_id`
- **List share links for one shared message**
  - `GET /message-sharelinks/shared-links/:message_id`
- **Get attachment signed URL**
  - `GET /message-sharelinks/:share_link_id/attachments/signedurl/:attachment_id`
- **Get audio file (redirect)**
  - `GET /message-sharelinks/:share_link_id/:audio_id`
- **Revoke access**
  - `PUT /message-sharelinks/revoke-access`
- **Update access for a link**
  - `PUT /message-sharelinks/:share_link_id/access`
- **List shares by message IDs (creator view)**
  - `POST /message-sharelinks/by-message-ids`

