# Frontend Integration

## Network setup

When testing from a phone or another device on the same LAN:

1. Set `HOST=0.0.0.0`
2. Set `PUBLIC_BASE_URL` to the machine IP, not `localhost`
3. Add frontend origins to `CORS_ORIGINS`
4. Keep `GOOGLE_CALLBACK_URL` on the same reachable host if Google auth is used

Example:

```env
HOST=0.0.0.0
PUBLIC_BASE_URL=http://192.168.1.12:4000
CORS_ORIGINS=http://192.168.1.12:3000,http://192.168.1.12:5173
GOOGLE_CALLBACK_URL=http://192.168.1.12:4000/api/v1/auth/google/callback
```

With `PUBLIC_BASE_URL` configured, file fields such as `avatar`, `coverImage`, `imageUrls`, `imageVariants`, `videoUrl`, `hlsUrl`, `audioUrl`, `thumbnailUrl`, `thumbnailVariants`, `mediaUrl`, and marketplace images are returned as absolute URLs.

## Pagination contract

List endpoints now keep the legacy fields and also return a unified `pagination` object.

Example:

```json
{
  "items": [],
  "count": 0,
  "page": 1,
  "limit": 20,
  "total": 0,
  "totalPages": 1,
  "nextCursor": null,
  "pagination": {
    "mode": "offset",
    "page": 1,
    "limit": 20,
    "count": 0,
    "total": 0,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPreviousPage": false,
    "nextPage": null,
    "previousPage": null,
    "currentCursor": null,
    "nextCursor": null
  }
}
```

Notes:

- Offset endpoints use `page` and `limit`
- Cursor-aware endpoints also return `nextCursor`
- Existing clients can keep reading the old top-level fields
- `sortOrder=asc|desc` is available on paginated endpoints

## Filter and sorting contract

Boolean query filters are parsed consistently now. Send:

- `?isActive=true`
- `?isActive=false`
- `?read=true`
- `?read=false`
- `?followingOnly=true`

Common conventions:

- `page`, `limit`, `sortOrder`
- `sortOrder` defaults to `desc`
- `sortBy` is supported on selected endpoints with endpoint-specific allowed values

Supported filters:

- `GET /feed/me`
  - defaults to followed accounts and the viewer's own posts
  - use `followingOnly=false` to widen the home feed to public discovery posts
  - `preferredPostType`, `followingOnly`, `radiusKm`, `includeSuggestions`, `suggestionInterval`
- `GET /marketplace/home`
  - `listingsLimit`, `instrumentsLimit`, `repairShopsLimit`, `onlyActive`
- `GET /users`
  - `q`, `isVerified`, `musicRole`, `experienceLevel`, `isPrivate`, `hasAvatar`, `sortBy`
  - `sortBy`: `createdAt`, `name`, `username`, `followersCount`, `postsCount`
- `GET /users/discover`
  - `q`, `musicRole`, `experienceLevel`, `hasAvatarOnly`, `includeRoleBuckets`, `sortBy`, `sortOrder`
- `GET /users/:id/profile-overview`
  - returns `stats`, `contentCounts`, `tabs`, and `viewerState`
- `GET /posts/user/:userId`
  - `visibility`, `postType`, `q`, `hashtag`, `sortBy`
  - `sortBy`: `createdAt`, `updatedAt`, `likesCount`, `commentsCount`, `savesCount`, `shareCount`, `viewCount`, `playCount`
- `GET /posts/reels`
  - `visibility`, `authorId`, `q`, `sortBy`
- `GET /marketplace/listings`
  - `q`, `minPrice`, `maxPrice`, `isActive`, `listingCategory`, `condition`, `instrumentType`, `sortBy`
  - `listingCategory`: `musical_instrument`, `accessory`, `audio_gear`, `sheet_music`, `other`
- `GET /marketplace/instruments`
  - `q`, `minPrice`, `maxPrice`, `isActive`, `condition`, `instrumentType`, `sortBy`
  - this route is now a musical-instruments-only view of marketplace listings
  - `sortBy`: `createdAt`, `updatedAt`, `price`, `title`
- `GET /marketplace/repair-shops`
  - `q`, `isActive`, `sortBy`
  - `sortBy`: `createdAt`, `updatedAt`, `name`
- `GET /notifications`
  - `read`, `type`, `resourceType`, `sortOrder`
- `GET /comments/post/:postId`
  - `page`, `limit`, `sortBy`, `sortOrder`
  - `sortBy`: `createdAt`, `top`
- `POST /comments/:commentId/replies`
  - JSON body: `content`
  - the route `commentId` is the parent comment; clients do not send `postId`
- `GET /comments/:commentId/replies`
  - `page`, `limit`, `sortBy`, `sortOrder`
  - `sortBy`: `createdAt`, `top`
- `PATCH /comments/:commentId`
  - JSON body: `content`, `mentionUsernames`

Comment list items include Instagram-like UI fields:

- `repliesCount`
- `repliesPreview`
- `likesCount`
- `likedByMe`
- `canEdit`
- `canDelete`
- `replyToUser`

## WebSocket auth

## Social upgrades

### Reposts and quote posts

- `POST /posts/:postId/repost`
  - body `{ "content": "" }` creates a plain repost
  - body `{ "content": "My take" }` creates a quote post
  - repost items expose `repostOfPostId`; quote items expose `quoteOfPostId`

### Reactions

Existing likes endpoints now accept `reactionType`:

- `like`
- `love`
- `haha`
- `wow`
- `sad`
- `angry`

Example:

```json
{
  "targetId": "...",
  "targetType": "post",
  "reactionType": "love"
}
```

`GET /likes/status/:targetType/:targetId` returns `reactionType` and `reactionSummary`.

### Private follow requests

When `targetUser.isPrivate=true`, `POST /follows/toggle` creates a pending request instead of following immediately.

- `GET /follows/requests`
- `PATCH /follows/requests/:requestId/approve`
- `PATCH /follows/requests/:requestId/reject`

`GET /follows/status/:targetUserId` returns `requested`.

### Reports

- `POST /reports`
  - `targetType`: `user`, `post`, `comment`, `listing`, `repair_shop`
  - `targetId`, `reason`, `details`
- `GET /reports/me`
- `GET /reports/superadmin`
- `PATCH /reports/superadmin/:reportId/status`

### Blocks

Global block endpoints:

- `POST /blocks/:targetUserId`
- `PATCH /blocks/:targetUserId/unblock`
- `GET /blocks/status/:targetUserId`

Blocked users are excluded from feed/trending/explore visibility.

### Explore

- `GET /feed/explore`
  - public discovery feed using the trending ranking path
  - respects global block visibility

Both namespaces accept the JWT access token in one of these places:

- `auth.token`
- `Authorization` header as `Bearer <token>`

## Chat namespace

Namespace: `/chat`

Client emits:

- `join_conversation`
  - payload: `{ "conversationId": "..." }`
- `send_message`
  - payload matches `SendMessageDto`
- `typing`
  - payload: `{ "conversationId": "...", "isTyping": true }`
- `mark_seen`
  - payload: `{ "messageId": "...", "conversationId": "..." }`

Server emits:

- `presence`
  - payload: `{ "userId": "...", "online": true }`
- `joined_conversation`
  - payload: `{ "conversationId": "..." }`
- `new_message`
  - payload: message object
- `typing`
  - payload: `{ "conversationId": "...", "userId": "...", "isTyping": true }`
- `message_seen`
  - payload: `{ "messageId": "...", "userId": "..." }`

## Notifications namespace

Namespace: `/notifications`

Server emits:

- `notification_created`
  - payload: notification object
- `notifications_unread_count`
  - payload: `{ "unreadCount": 3 }`

Notification types currently used:

- `like`
- `comment`
- `follow`
- `message`
- `save`
- `share`
- `mention`

## Mentions

Posts and comments support:

- `taggedUserIds`: official tagged users by Mongo id
- `mentionUsernames`: usernames such as `["rami_sabry"]`

The backend also extracts `@username` from `content` automatically and emits mention notifications for matched users.

## Posts Upload Contract

Post creation and updates now require `multipart/form-data` for all post payloads, including text-only posts and posts that use external media links.

- `POST /posts`
- `PATCH /posts/:postId`
- `POST /posts/reels`

Send media uploads as files:

- `imageFiles`
- `videoFile`
- `audioFile`

Send link-based fields as text fields inside the same form-data payload:

- `imageUrls`
- `videoUrl`
- `audioUrl`
- `thumbnailUrl`

Array fields may be sent either as repeated form keys or JSON text:

- `taggedUserIds`
- `mentionUsernames`
- `imageUrls`
- `waveformPeaks`

## Video optimization

When `VIDEO_PROCESSING_ENABLED=true` and `ffmpeg` is available on the server:

- uploaded post/reel videos are converted to optimized `mp4`
- `+faststart` is applied so playback begins faster on mobile/web
- uploaded post/reel videos also produce an HLS playlist at `hlsUrl` when `VIDEO_PROCESSING_GENERATE_HLS=true`
- when `ffprobe` is available, `hlsUrl` points to a master playlist with multiple renditions so HLS players can downgrade quality automatically on weak networks
- local storage responses serve `mp4`, `m3u8`, `m4s`, and `ts` files with explicit media `Content-Type` headers and `Accept-Ranges: bytes`
- a thumbnail image is generated automatically if the client does not send `thumbnailUrl`
- generated thumbnails also expose `thumbnailVariants.lowUrl`, `thumbnailVariants.mediumUrl`, `thumbnailVariants.highUrl`, and `thumbnailVariants.originalUrl`

If `ffmpeg` is not installed or video processing is disabled, uploads still work and the original
video file is stored as-is.

Recommended client behavior:

- use `hlsUrl` first when present for adaptive/streaming playback
- fall back to `videoUrl` for progressive `mp4` playback
- for poster frames on weak networks, prefer `thumbnailVariants.lowUrl` or `thumbnailVariants.mediumUrl`

## Audio and image delivery

Managed uploads are stored under stable UUID-based paths, so local storage responses now send cache-friendly headers:

- images (`jpg`, `jpeg`, `png`, `webp`, `gif`) are served with explicit `Content-Type` and long-lived immutable `Cache-Control`
- audio (`mp3`, `wav`, `m4a`, `aac`, `ogg`) is served with explicit `Content-Type`, long-lived immutable `Cache-Control`, and `Accept-Ranges: bytes`
- uploaded post images are normalized into responsive variants and posts now include `imageVariants[]`

Recommended client behavior:

- for audio, stream `audioUrl` directly and allow byte-range playback/resume
- `imageUrls` now default to feed-friendly optimized images for backward compatibility
- on weak networks, prefer `imageVariants[index].lowUrl`
- on normal networks, use `imageUrls[index]` or `imageVariants[index].mediumUrl`
- on detail screens or zoom views, use `imageVariants[index].highUrl` or `imageVariants[index].originalUrl`

## Instagram-style social controls

Posts now support:

- carousel metadata through `imageItems[]` with `url`, `caption`, `altText`, and `order`
- `collaboratorIds[]` on create/update
- profile pinning through `PATCH /posts/:postId/pin-profile` and `/unpin-profile`
- archive/restore through `PATCH /posts/:postId/archive` and `/restore-archive`
- per-post comment settings through `PATCH /posts/:postId/comment-settings`

Comment settings:

- `commentsDisabled`
- `commentsFollowersOnly`
- `commentFilterKeywords[]`

Comments now support:

- `PATCH /comments/:commentId/pin`
- `PATCH /comments/:commentId/unpin`
- hidden offensive/filter matches via `hiddenByFilter`

Chat now supports:

- message media upload through `POST /chat/messages/upload`
- replies with `replyToMessageId`
- reactions through `PATCH /chat/messages/:messageId/reaction`
- delete-for-me through `PATCH /chat/messages/:messageId/delete-for-me`

Reports now require fixed `reason` values:

- `spam`, `harassment`, `hate_speech`, `nudity`, `violence`, `scam`, `intellectual_property`, `self_harm`, `other`

When a post/comment reaches multiple open reports, the backend can automatically flag it for moderation. User reports can include `blockTarget: true` to block the reported user immediately.

## Marketplace split

Marketplace is now separated from musical instruments at the API contract level:

- `GET /marketplace/listings`
  - general sale listings across all listing categories
- `GET /marketplace/instruments`
  - only musical instruments
- `GET /marketplace/repair-shops`
  - service providers and maintenance shops
- `GET /marketplace/home`
  - frontend-friendly grouped payload with `categories`, `summary`, `filters.listingCategories`, `featuredShops`, and `sections`

Admin endpoints follow the same split:

- `POST /marketplace/admin/listings`
- `GET /marketplace/admin/listings/me`
- `PATCH /marketplace/admin/listings/:id`
- `DELETE /marketplace/admin/listings/:id`

The older `/marketplace/admin/instruments*` routes still exist and always force `listingCategory=musical_instrument` for backward compatibility.

Marketplace shop ownership rule:

- each admin can own one repair-shop / marketplace shop record only
- if the admin already created one, the frontend should call update endpoints instead of create

Listing responses now also include:

- `shop`
  - `{ adminId, name, username, avatar }`
- `storeName`
  - direct shortcut for list cards
- `condition`
  - `new`, `like_new`, `used`, `refurbished`
- `instrumentType`
  - free-text type such as `oud`, `piano`, or `violin`

Search on marketplace listings now matches:

- listing title
- listing description
- instrument type
- shop name

## Talent discovery

The talents screen can now be built from:

- `GET /users/discover`
  - paginated talent cards
  - `roleBuckets` counts for tabs such as instrumentalists, singers, producers, and lyricists
- `GET /users/:id/profile-overview`
  - summary for the profile header and tab counts

`profile-overview` returns:

- `stats.followersCount`
- `stats.followingCount`
- `stats.postsCount`
- `stats.collaborationsCount`
- `contentCounts.reels`
- `contentCounts.audio`
- `contentCounts.other`
- `viewerState.following`

## Smoke / e2e

Run:

```bash
npm run test:e2e
```

The smoke suite covers health, auth verification, profile setup, image upload, post creation, feed retrieval, notifications, and comment mentions.
