TAG API — CHAT, SEARCH & MOMENTS
=======================
Base URL : https://tag.nsamaandcompany.com/tag_api
Auth     : Authorization: Bearer <token>  required on all endpoints


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DIRECT MESSAGES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

GET  /api/v1/conversations
  List all your conversations with unread counts and last message preview.

POST /api/v1/conversations
  Start a new DM or return an existing one.
  Body: { "recipient_id": 42 }

GET  /api/v1/conversations/unread-count
  Total unread messages across all conversations. Use for tab badge.
  Returns: { "unread_count": 5 }

GET  /api/v1/conversations/{id}
  Get a single conversation and the other person's profile.

GET  /api/v1/conversations/{id}/messages?page=1&limit=30
  Paginated message history, oldest first.
  Returns: { page, limit, has_more, messages[] }

POST /api/v1/conversations/{id}/messages
  Send a text message:
    Content-Type: application/json
    Body: { "type": "text", "body": "hello" }
  Send an image/audio/video:
    Content-Type: multipart/form-data
    Fields: type = "image" | "audio" | "video",  media = <file>

PATCH /api/v1/conversations/{id}/read
  Mark all messages in this conversation as read.
  Call when the user opens the conversation screen.

DELETE /api/v1/conversations/{id}/messages/{msgId}
  Delete your own message. Only the sender can delete.

--- Real-time ---
Listen to Firebase RTDB:  chats/direct/{conversation_id}/last_event
When it changes, re-fetch messages or refresh the conversation list.

FCM push payload:
  { "type": "direct_message", "conversation_id": "7" }


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SPACE CHAT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Note: User must join the space first.
      Senders appear as space_tag_code (anonymous), not by name.

GET  /api/v1/tag-spaces/{uuid}/chat?page=1&limit=50
  Get chat message history for a space.
  Returns: { page, limit, has_more, unread_count, messages[] }
  Each message has: id, type, body, media_url, sent_at, sender_code
  Note: unread_count drops to 0 after calling PATCH /chat/read.

POST /api/v1/tag-spaces/{uuid}/chat
  Send a text message:
    Content-Type: application/json
    Body: { "type": "text", "body": "hey everyone" }
  Send an image/audio/video:
    Content-Type: multipart/form-data
    Fields: type = "image" | "audio" | "video",  media = <file>
  Returns: { id, type, body, media_url, sender_code, sent_at }

PATCH /api/v1/tag-spaces/{uuid}/chat/read
  Mark all messages in this space as read for the current user.
  Call when the user opens the space chat screen.

DELETE /api/v1/tag-spaces/{uuid}/chat/{id}
  Delete your own message. Only the sender can delete.

--- Real-time ---
Listen to Firebase RTDB:  chats/spaces/{uuid}/last_event
When it changes, re-fetch latest messages.

FCM push payload:
  { "type": "space_message", "space_uuid": "abc-123", "sender_code": "145223a8ab0c" }


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MOMENTS  (24-hour disappearing status)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Note: Expired moments (>24h) are excluded automatically from all responses.
      Max 10 active moments per user at a time.

GET  /api/v1/moments
  Returns your own active moments and your contacts' active moments grouped by user.
  Response:
    my_moments: { has_active, moments[] }
    contacts:   [ { user, has_unseen, moments_count, moments[] } ]
  Each moment has: id, media_type, media_url, caption, background_color, created_at, expires_at
  Own moments also include: views_count
  Contact moments also include: is_viewed (bool)
  Contacts ordered: unseen first, then by recency.

POST /api/v1/moments
  Upload a new moment.
  Send text moment:
    Content-Type: application/json
    Body: { "type": "text", "caption": "good morning", "background_color": "#DC143C" }
    Note: caption or background_color required for text type.
  Send image or video:
    Content-Type: multipart/form-data
    Fields: type = "image" | "video",  media = <file>
  Returns: { id, media_type, media_url, caption, background_color, created_at, expires_at }

PATCH /api/v1/moments/{id}/seen
  Mark a moment as viewed. Idempotent — safe to call multiple times.
  Cannot mark your own moment as seen (returns 403).

GET  /api/v1/moments/{id}/viewers
  List everyone who has viewed a moment.
  Only the moment owner can call this (returns 403 otherwise).
  Returns: [ { user_id, full_name, avatar_url, public_tag_id, viewed_at } ]

DELETE /api/v1/moments/{id}
  Delete your own moment before it expires. Only owner can delete.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SEARCH
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

GET  /api/v1/search/snippets?q=flutter
  Search public posts.

GET  /api/v1/search/vibes?q=music
  Search tag vibes.

GET  /api/v1/search/spaces?q=programming
  Search tag spaces.

GET  /api/v1/search/trending
  Get trending content. No query param needed.
