TAG API — POST & VIBE INTERACTIONS
=====================================
Base URL : https://tag.nsamaandcompany.com/tag_api
Auth     : Authorization: Bearer <token>  required on all endpoints

Covers: post/vibe delete, view tracking, share to contacts, comment listing,
        comment deletion, comment reactions (like/toggle), vibe bookmarks.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PUBLIC POSTS  (additional endpoints)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

GET  /api/v1/public-posts                       [AUTH]
  Query params (optional):
    author_tag = "johndoe"  — filter feed to posts by a specific user.
    (omit to get the full public timeline)

POST /api/v1/public-posts/{id}/view             [AUTH]
  Record a view on a public post. Increments views_count.
  Path param: id — the post id
  Body: (none)
  Response 200: (no data)
  Errors:
    404 — post not found

DELETE /api/v1/public-posts/{id}                [AUTH]
  Soft-delete your own public post.
  Path param: id — the post id
  Body: (none)
  Response 200: { "message": "Post deleted successfully." }
  Errors:
    403 — post belongs to someone else
    404 — post not found

GET  /api/v1/public-posts/{id}/comments         [AUTH]
  Fetch all comments on a public post.
  Path param: id — the post id
  Returns: [
    {
      id, parent_comment_id, body,
      reactions_count, is_liked,
      author: { public_tag_id, avatar_url },
      created_at
    }
  ]
  Note: Replies are nested by parent_comment_id. Flat list returned;
        group client-side by parent_comment_id.
  Errors:
    404 — post not found

POST /api/v1/public-posts/{postId}/comments/{commentId}/reactions/toggle  [AUTH]
  Toggle a reaction on a comment of a public post.
  Path params: postId, commentId
  Body: { "reaction_type": "like" | "love" | "fire" | "laugh" | "sad" | "angry" }
  Response 200 (removed): { reactions_count, is_liked: false }
  Response 200 (changed): { reactions_count, is_liked: true }
  Response 201 (added):   { reactions_count, is_liked: true }
  Errors:
    422 — validation (missing or invalid reaction_type)
    404 — post not found | comment not found

POST /api/v1/public-posts/{id}/share-to-contacts  [AUTH]
  Send a public post as a DM to one or more contacts in a single call.
  Path param: id — the post id
  Body: { "recipient_ids": [1, 2, 3] }
  Response 201:
    {
      "sent": [
        { "conversation_id": 10, "message_id": 55, "recipient_id": 1 },
        { "conversation_id": 11, "message_id": 56, "recipient_id": 2 }
      ]
    }
  Notes:
    - Creates a new conversation if one does not already exist.
    - Sending yourself is silently skipped.
    - Message type in the conversation will be "post_share".
  Errors:
    404 — post not found
    422 — recipient_ids missing or empty


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TAG SPACE POSTS  (additional endpoints)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

DELETE /api/v1/tag-spaces/posts/{id}            [AUTH]
  Soft-delete your own space post.
  Path param: id — the space post id (numeric)
  Body: (none)
  Response 200: { "message": "Post deleted successfully." }
  Errors:
    403 — post belongs to someone else
    404 — post not found

POST /api/v1/tag-spaces/posts/{postId}/comments/{commentId}/reactions/toggle  [AUTH]
  Toggle a reaction on a comment inside a space post.
  Path params: postId, commentId
  Body: { "reaction_type": "like" | "love" | "fire" | "laugh" | "sad" | "angry" }
  Response 200/201: { reactions_count, is_liked }
  Errors:
    404 — post not found | comment not found
    422 — missing or invalid reaction_type


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TAG VIBES  (additional endpoints)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

--- FEED FILTERS ---

GET  /api/v1/tag-vibes                          [AUTH]
  Query params (optional):
    cursor    = <last_vibe_id>   — cursor-based pagination (default: 0 = start)
    author_tag = "johndoe"       — filter to vibes by a specific user
  Returns: { items: [...], next_cursor: <id | null> }
  Note: Fetch the next page by passing next_cursor as cursor.
        next_cursor is null when there are no more items.

--- BOOKMARKS ---

GET  /api/v1/tag-vibes/bookmarked               [AUTH]
  Get your saved/bookmarked vibes.
  Query params (optional):
    cursor = <last_bookmark_id>  — cursor-based pagination (default: 0)
  Returns: { items: [...], next_cursor: <id | null> }
  (Same vibe shape as GET /tag-vibes)

POST /api/v1/tag-vibes/{id}/bookmark            [AUTH]
  Toggle a bookmark on a vibe (add if not bookmarked, remove if already bookmarked).
  Path param: id — the vibe id
  Body: (none)
  Response 200: { "is_bookmarked": true | false }
  Errors:
    404 — vibe not found

--- VIEW TRACKING ---

POST /api/v1/tag-vibes/{id}/view                [AUTH]
  Record a view on a vibe. Increments views_count.
  Path param: id — the vibe id
  Body: (none)
  Response 200: (no data)
  Errors:
    404 — vibe not found

--- SHARE TO CONTACTS ---

POST /api/v1/tag-vibes/{id}/share-to-contacts   [AUTH]
  Send a vibe as a DM to one or more contacts.
  Path param: id — the vibe id
  Body: { "recipient_ids": [1, 2, 3] }
  Response 201:
    {
      "sent": [
        { "conversation_id": 10, "message_id": 55, "recipient_id": 1 }
      ]
    }
  Notes:
    - Creates a new conversation if one does not exist.
    - Message type will be "vibe_share".
  Errors:
    404 — vibe not found
    422 — recipient_ids missing or empty

--- COMMENTS ---

GET  /api/v1/tag-vibes/{id}/comments            [AUTH]
  Paginated list of top-level comments on a vibe.
  Path param: id — the vibe id
  Query params:
    page  (default 1)
    limit (default 50, max 100)
  Returns:
    {
      page, limit, total, has_more,
      comments: [
        {
          id, body, reactions_count, replies_count, is_liked,
          author: { public_tag_id, avatar_url },
          created_at
        }
      ]
    }
  Errors:
    404 — vibe not found

GET  /api/v1/tag-vibes/{vibeId}/comments/{commentId}/replies  [AUTH]
  Fetch all direct replies to a comment.
  Path params: vibeId, commentId
  Returns: [
    {
      id, parent_comment_id, body, reactions_count, is_liked,
      author: { public_tag_id, avatar_url },
      created_at
    }
  ]
  Errors:
    404 — vibe not found | comment not found

DELETE /api/v1/tag-vibes/{vibeId}/comments/{commentId}  [AUTH]
  Delete your own comment on a vibe.
  Path params: vibeId, commentId
  Body: (none)
  Response 200: { "message": "Comment deleted." }
  Errors:
    403 — comment belongs to someone else
    404 — vibe not found | comment not found

--- COMMENT REACTIONS ---

POST /api/v1/tag-vibes/{vibeId}/comments/{commentId}/reactions/toggle  [AUTH]
  Toggle a reaction on a vibe comment.
  Path params: vibeId, commentId
  Body: { "reaction_type": "like" | "love" | "fire" | "laugh" | "sad" | "angry" }
  Response 200 (removed): { reactions_count, is_liked: false }
  Response 200 (changed): { reactions_count, is_liked: true }
  Response 201 (added):   { reactions_count, is_liked: true }
  Errors:
    404 — vibe not found | comment not found
    422 — missing or invalid reaction_type
