# Tank Track — Connections Module API

Frontend/mobile integration guide for the Friends / Connections feature.

---

## Base URL

| Environment | Base URL |
|-------------|----------|
| Local | `http://localhost:6260/api/v1/connections` |
| Staging / Production | `https://<your-api-host>/api/v1/connections` |

All routes are mounted at `{API_PREFIX}/connections` (default prefix: `/api/v1`).

---

## Authentication (required on every endpoint)

Every connections endpoint requires a valid JWT from login/signup.

### Request headers

```http
Authorization: Bearer <access_token>
Content-Type: application/json
```

| Header | Required | Value |
|--------|----------|-------|
| `Authorization` | Yes | `Bearer <jwt_token>` |
| `Content-Type` | Yes (POST/PATCH) | `application/json` |

### Unauthorized responses

**401 — Missing token**

```json
{
  "message": "UnAuthorized Request"
}
```

**401 — Invalid / expired token**

```json
{
  "message": "Invalid Token"
}
```

---

## Standard response envelope

### Success (`200`)

```json
{
  "status": 200,
  "success": true,
  "message": "Human-readable success message",
  "data": {}
}
```

### Error (business logic)

```json
{
  "statusCode": 400,
  "success": false,
  "message": "Error message string or validation array",
  "data": {}
}
```

> **Note:** Success responses use `"status"`. Error responses use `"statusCode"`. This matches the existing Tank Track API pattern.

### Validation error (`400`)

```json
{
  "statusCode": 400,
  "success": false,
  "message": [
    {
      "field": "email",
      "message": "Invalid email format"
    }
  ],
  "data": {}
}
```

---

## Data model (`connections` collection)

Each connection record represents a relationship between users.

| Field | Type | Description |
|-------|------|-------------|
| `_id` | ObjectId | Connection / request document ID. Use as `requestId` for accept, reject, cancel. |
| `senderId` | ObjectId | User who sent the invite or request |
| `receiverId` | ObjectId \| null | Registered user who received the request. `null` for email-only invites. |
| `inviteeEmail` | string \| null | Email address when inviting someone not yet registered. `null` for user-to-user requests. |
| `status` | string | `pending` \| `accepted` \| `rejected` \| `cancelled` |
| `createdAt` | ISO date | When the request was created |
| `updatedAt` | ISO date | Last status change (e.g. when accepted) |

### Record types

| Scenario | `receiverId` | `inviteeEmail` | `status` |
|----------|--------------|----------------|----------|
| Request to registered user | set | `null` | `pending` → `accepted` / `rejected` |
| Invite to unregistered email | `null` | set | `pending` (until they sign up — future enhancement) |
| Friends (connected) | set | `null` | `accepted` |
| Sender cancelled | set or email | varies | `cancelled` |
| Receiver rejected | set | `null` | `rejected` |

### Important IDs for the mobile app

| ID | Where it comes from | Used for |
|----|---------------------|----------|
| `requestId` | `POST /invite` response, or `GET /requests` list | Accept / reject / cancel |
| `user._id` | `GET /my-connections`, `GET /nearby-users`, `GET /requests` | Unfriend (`DELETE /:userId`), display profile |
| `connection document _id` | MongoDB internal | **Do not** use for unfriend — use the friend's **user `_id`** instead |

---

## Endpoints overview

| # | Method | Path | Description |
|---|--------|------|-------------|
| 1 | `POST` | `/invite` | Send connection request or email invite |
| 2 | `GET` | `/nearby-users` | Find nearby users with connection status |
| 3 | `GET` | `/my-connections` | List accepted friends |
| 4 | `GET` | `/requests` | List pending sent or received requests |
| 5 | `PATCH` | `/:requestId/accept` | Accept a received request |
| 6 | `PATCH` | `/:requestId/reject` | Reject a received request |
| 7 | `PATCH` | `/:requestId/cancel` | Cancel a sent request |
| 8 | `DELETE` | `/:userId` | Unfriend (remove accepted connection) |

---

## 1. Invite or send connection request

Send a friend request to a registered user, or send an email invite if the email is not registered.

### Request

```http
POST /api/v1/connections/invite
Authorization: Bearer <token>
Content-Type: application/json
```

**Body**

```json
{
  "email": "friend@example.com"
}
```

| Field | Type | Required | Rules |
|-------|------|----------|-------|
| `email` | string | Yes | Valid email, max 255 chars |

### Success — registered user (connection request)

```json
{
  "status": 200,
  "success": true,
  "message": "Connection request sent",
  "data": {
    "requestId": "684a1b2c3d4e5f6789012345",
    "inviteType": "user"
  }
}
```

### Success — unregistered email (email invite)

```json
{
  "status": 200,
  "success": true,
  "message": "Invitation email sent",
  "data": {
    "requestId": "684a1b2c3d4e5f6789012346",
    "inviteType": "email"
  }
}
```

| `inviteType` | Meaning |
|--------------|---------|
| `"user"` | Target is a registered user; pending request created with `receiverId` |
| `"email"` | Target not registered; pending record created with `inviteeEmail` + email sent |

### Error responses

| HTTP | Message |
|------|---------|
| `400` | `"You cannot send a connection request to yourself"` |
| `403` | `"This email is associated with a banned account. Please contact support for further assistance."` |
| `409` | `"You are already connected with this user"` |
| `409` | `"A connection request already exists between you and this user"` |

---

## 2. Nearby users

Find verified, active users near the caller's location. Includes connection status for each user.

### Request

```http
GET /api/v1/connections/nearby-users?latitude=33.6844&longitude=73.0479&radius=5&page=1&limit=20
Authorization: Bearer <token>
```

**Query parameters**

| Param | Type | Required | Default | Rules |
|-------|------|----------|---------|-------|
| `latitude` | number | Yes | — | -90 to 90 |
| `longitude` | number | Yes | — | -180 to 180 |
| `radius` | number | No | `5` | Kilometers, max 100 |
| `page` | number | No | `1` | Positive integer |
| `limit` | number | No | `20` | Max 100 |

### Success response

```json
{
  "status": 200,
  "success": true,
  "message": "Nearby users fetched successfully",
  "data": {
    "data": [
      {
        "_id": "683ef251ce7cbc33d8946b9c",
        "fullName": "jack",
        "email": "jack@yopmail.com",
        "image": "http://localhost:6260/public/uploads/1000000062-1782129896819.jpg",
        "distanceKm": 1.25,
        "connectionStatus": "none"
      },
      {
        "_id": "684a1b2c3d4e5f6789012347",
        "fullName": "sara",
        "email": "sara@yopmail.com",
        "image": null,
        "distanceKm": 2.80,
        "connectionStatus": "pending_sent"
      }
    ],
    "total": 2,
    "page": 1,
    "totalPages": 1
  }
}
```

### `connectionStatus` values (use for UI buttons)

| Value | UI suggestion |
|-------|---------------|
| `"none"` | Show **Add Friend** / **Connect** |
| `"pending_sent"` | Show **Request Sent** (disabled or cancel option) |
| `"pending_received"` | Show **Accept** / **Reject** |
| `"connected"` | Show **Connected** / **Message** / unfriend option |

> Users must have a saved GPS location and `isVerified: true`. Banned and deleted users are excluded.

---

## 3. My connections (friends list)

List all accepted connections (friends).

### Request

```http
GET /api/v1/connections/my-connections?page=1&limit=20&search=jack
Authorization: Bearer <token>
```

**Query parameters**

| Param | Type | Required | Default | Rules |
|-------|------|----------|---------|-------|
| `page` | number | No | `1` | Positive integer |
| `limit` | number | No | `20` | Max 100 |
| `search` | string | No | — | Filter by `fullName` or `email`, max 200 chars |

### Success response

```json
{
  "status": 200,
  "success": true,
  "message": "Connections fetched successfully",
  "data": {
    "data": [
      {
        "_id": "683ef251ce7cbc33d8946b9c",
        "fullName": "jack",
        "email": "jack@yopmail.com",
        "image": "http://localhost:6260/public/uploads/1000000062-1782129896819.jpg",
        "connectedAt": "2026-07-07T11:54:38.823Z"
      }
    ],
    "total": 1,
    "page": 1,
    "totalPages": 1
  }
}
```

| Field | Description |
|-------|-------------|
| `_id` | **Friend's user ID** — use this for `DELETE /:userId` (unfriend) |
| `image` | Full absolute URL, or `null` if no profile image |
| `connectedAt` | When the connection was accepted |

### Empty list

```json
{
  "status": 200,
  "success": true,
  "message": "Connections fetched successfully",
  "data": {
    "data": [],
    "total": 0,
    "page": 1,
    "totalPages": 0
  }
}
```

---

## 4. Connection requests (sent / received)

List pending connection requests.

### Request

```http
GET /api/v1/connections/requests?type=received&page=1&limit=20
Authorization: Bearer <token>
```

**Query parameters**

| Param | Type | Required | Default | Rules |
|-------|------|----------|---------|-------|
| `type` | string | Yes | — | `"received"` or `"sent"` |
| `page` | number | No | `1` | Positive integer |
| `limit` | number | No | `20` | Max 100 |

### Success — received requests

```json
{
  "status": 200,
  "success": true,
  "message": "Connection requests fetched successfully",
  "data": {
    "data": [
      {
        "requestId": "684a1b2c3d4e5f6789012345",
        "inviteType": "user",
        "user": {
          "_id": "683ef251ce7cbc33d8946b9c",
          "fullName": "jack",
          "email": "jack@yopmail.com",
          "image": "http://localhost:6260/public/uploads/1000000062-1782129896819.jpg"
        },
        "requestedAt": "2026-07-07T10:30:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "totalPages": 1
  }
}
```

### Success — sent requests (registered user)

```json
{
  "status": 200,
  "success": true,
  "message": "Connection requests fetched successfully",
  "data": {
    "data": [
      {
        "requestId": "684a1b2c3d4e5f6789012345",
        "inviteType": "user",
        "user": {
          "_id": "684a1b2c3d4e5f6789012347",
          "fullName": "sara",
          "email": "sara@yopmail.com",
          "image": null
        },
        "requestedAt": "2026-07-07T10:30:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "totalPages": 1
  }
}
```

### Success — sent requests (email invite, user not registered)

```json
{
  "status": 200,
  "success": true,
  "message": "Connection requests fetched successfully",
  "data": {
    "data": [
      {
        "requestId": "684a1b2c3d4e5f6789012346",
        "inviteType": "email",
        "inviteeEmail": "jim@yopmail.com",
        "user": {
          "_id": null,
          "fullName": null,
          "email": "jim@yopmail.com",
          "image": null
        },
        "requestedAt": "2026-07-07T10:30:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "totalPages": 1
  }
}
```

| Field | Description |
|-------|-------------|
| `requestId` | Use for accept / reject / cancel |
| `inviteType` | `"user"` or `"email"` |
| `inviteeEmail` | Present only for `inviteType: "email"` |

---

## 5. Accept request

Accept a **received** pending request. Only the receiver can accept.

### Request

```http
PATCH /api/v1/connections/684a1b2c3d4e5f6789012345/accept
Authorization: Bearer <token>
Content-Type: application/json
```

**Path parameter**

| Param | Description |
|-------|-------------|
| `requestId` | Connection document `_id` from `GET /requests?type=received` |

**Body:** none required (empty body is fine)

### Success response

```json
{
  "status": 200,
  "success": true,
  "message": "Connection request accepted",
  "data": {}
}
```

### Error responses

| HTTP | Message |
|------|---------|
| `403` | `"You do not have permission to perform this action"` |
| `404` | `"Connection request not found"` |
| `409` | `"This request cannot be modified in its current state"` |

---

## 6. Reject request

Reject a **received** pending request. Only the receiver can reject.

### Request

```http
PATCH /api/v1/connections/684a1b2c3d4e5f6789012345/reject
Authorization: Bearer <token>
Content-Type: application/json
```

### Success response

```json
{
  "status": 200,
  "success": true,
  "message": "Connection request rejected",
  "data": {}
}
```

Same error codes as accept.

---

## 7. Cancel request

Cancel a **sent** pending request. Only the sender can cancel.

### Request

```http
PATCH /api/v1/connections/684a1b2c3d4e5f6789012345/cancel
Authorization: Bearer <token>
Content-Type: application/json
```

**Path parameter**

| Param | Description |
|-------|-------------|
| `requestId` | Connection document `_id` from `GET /requests?type=sent` or `POST /invite` response |

### Success response

```json
{
  "status": 200,
  "success": true,
  "message": "Connection request cancelled",
  "data": {}
}
```

Same error codes as accept.

---

## 8. Remove connection (unfriend)

Remove an **accepted** friendship. Pass the **friend's user `_id`**, not the connection document ID.

### Request

```http
DELETE /api/v1/connections/683ef251ce7cbc33d8946b9c
Authorization: Bearer <token>
```

**Path parameter**

| Param | Description |
|-------|-------------|
| `userId` | Friend's user `_id` from `GET /my-connections` → `data.data[]._id` |

### Success response

```json
{
  "status": 200,
  "success": true,
  "message": "Connection removed",
  "data": {}
}
```

### Error responses

| HTTP | Message |
|------|---------|
| `400` | `"You cannot send a connection request to yourself"` (if you pass your own ID) |
| `404` | `"Connection not found"` |

---

## Mobile app — recommended screens & flows

### Screen: Find Friends / Nearby

1. Get device GPS → call `GET /nearby-users`
2. Render list with `connectionStatus` driving button state
3. On **Connect** → `POST /invite` with user's email (or add a direct user-id flow later)
4. Refresh list after invite

### Screen: Friend Requests

**Tabs: Received | Sent**

- Received tab → `GET /requests?type=received`
  - **Accept** → `PATCH /:requestId/accept`
  - **Reject** → `PATCH /:requestId/reject`
- Sent tab → `GET /requests?type=sent`
  - **Cancel** → `PATCH /:requestId/cancel`
  - For `inviteType: "email"` → show email + "Invite pending" (no accept/reject on receiver side until they register)

### Screen: My Friends

1. `GET /my-connections` with optional search
2. Tap friend → profile screen
3. **Unfriend** → `DELETE /:userId` using friend's `_id` from the list

### Screen: Invite by email

1. User enters email → `POST /invite`
2. Store `requestId` from response for cancel later
3. Show success based on `inviteType`:
   - `"user"` → "Request sent"
   - `"email"` → "Invitation email sent"

---

## Pagination pattern (all list endpoints)

All list endpoints return the same pagination shape inside `data`:

```json
{
  "data": [],
  "total": 0,
  "page": 1,
  "totalPages": 0
}
```

**Load more:** increment `page` until `page >= totalPages`.

---

## Image URLs

Profile images are returned as **full absolute URLs**:

```
http://localhost:6260/public/uploads/<filename>.jpg
```

Production uses your API host from `PUBLIC_API_URL` / `BASE_URL`. If `image` is `null`, show a default avatar.

---

## Status lifecycle

```
                    POST /invite
                         │
           ┌─────────────┴─────────────┐
           ▼                           ▼
   [registered user]            [unregistered email]
   receiverId set               inviteeEmail set
   status: pending              status: pending
           │                           │
     accept │ reject              cancel (sender)
           │     │                     │
           ▼     ▼                     ▼
      accepted rejected            cancelled
           │
    DELETE /:userId (unfriend)
           │
      (document deleted)
```

- **Rejected** and **cancelled** records are kept in the database (not deleted).
- After reject/cancel, a new invite can be sent to the same person.
- **Unfriend** hard-deletes the accepted connection document.

---

## Quick reference — which ID to use

| Action | ID to pass | Example source |
|--------|------------|----------------|
| Accept request | `requestId` | `GET /requests?type=received` → `data.data[].requestId` |
| Reject request | `requestId` | `GET /requests?type=received` → `data.data[].requestId` |
| Cancel request | `requestId` | `GET /requests?type=sent` → `data.data[].requestId` |
| Unfriend | friend's `userId` | `GET /my-connections` → `data.data[]._id` |
| Invite | email in body | User input |

---

## Source files (backend)

| File | Purpose |
|------|---------|
| `src/routes/connectionRoutes.ts` | Route definitions |
| `src/controllers/connectionController.ts` | HTTP handlers |
| `src/services/connectionService.ts` | Business logic |
| `src/models/ConnectionModel.ts` | MongoDB schema |
| `src/validators/connectionValidator/index.ts` | Request validation |
| `src/constants/messages.ts` | `CONNECTION_CONSTANTS` messages |

---

## Example: full flow (cURL)

```bash
# 1. Login first (get token from auth module)
TOKEN="eyJhbGciOiJIUzI1NiIs..."

# 2. Send invite
curl -X POST "http://localhost:6260/api/v1/connections/invite" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"friend@yopmail.com"}'

# 3. List sent requests
curl "http://localhost:6260/api/v1/connections/requests?type=sent&page=1&limit=20" \
  -H "Authorization: Bearer $TOKEN"

# 4. Nearby users
curl "http://localhost:6260/api/v1/connections/nearby-users?latitude=33.6844&longitude=73.0479&radius=5" \
  -H "Authorization: Bearer $TOKEN"

# 5. My friends
curl "http://localhost:6260/api/v1/connections/my-connections?page=1&limit=20" \
  -H "Authorization: Bearer $TOKEN"

# 6. Accept (receiver only)
curl -X PATCH "http://localhost:6260/api/v1/connections/<requestId>/accept" \
  -H "Authorization: Bearer $TOKEN"

# 7. Unfriend (use friend's user _id)
curl -X DELETE "http://localhost:6260/api/v1/connections/<friendUserId>" \
  -H "Authorization: Bearer $TOKEN"
```
