Social API

API endpoints for social features - following users, activity feeds, likes, and comments.

The Social API provides endpoints for managing social interactions between users, including following, activity feeds, likes, and comments.

Base URL

Text
/api/v1/social

Authentication

All social endpoints require authentication via session cookie or API key.


Following

Follow a User

POST /api/v1/social

Follow another user to see their activity in your feed.

Request:

JSON
{
  "followingId": "uuid"
}

Response:

JSON
{
  "success": true,
  "data": {
    "follower_id": "uuid",
    "following_id": "uuid",
    "created_at": "2024-01-15T10:30:00Z"
  }
}

Error Codes: | Code | Description | |------|-------------| | VALIDATION_ERROR | Cannot follow yourself | | VALIDATION_ERROR | Already following this user | | NOT_FOUND | User not found |

Example:

JavaScript
const response = await fetch('/api/v1/social', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ followingId: 'user-uuid-here' }),
});

const result = await response.json();
if (result.success) {
  console.log('Now following user!');
}

Unfollow a User

DELETE /api/v1/social

Stop following a user.

Query Parameters or Body: | Parameter | Type | Description | |-----------|------|-------------| | followingId | uuid | User ID to unfollow |

Request (via body):

JSON
{
  "followingId": "uuid"
}

Or via query string:

Text
DELETE /api/v1/social?followingId=uuid

Response:

JSON
{
  "success": true,
  "data": {
    "unfollowed": true
  }
}

Error Codes: | Code | Description | |------|-------------| | VALIDATION_ERROR | User ID required | | NOT_FOUND | Follow relationship not found |

Example:

JavaScript
const response = await fetch('/api/v1/social', {
  method: 'DELETE',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ followingId: 'user-uuid-here' }),
});

User Profiles

Get User Profile

GET /api/v1/social

Retrieve a user’s profile with stats and social information.

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | userId | uuid | Required: User ID to fetch |

Response:

JSON
{
  "success": true,
  "data": {
    "user": {
      "id": "uuid",
      "username": "beerlover",
      "email": "user@example.com",
      "first_name": "John",
      "last_name": "Doe",
      "avatar_url": "https://cdn.brewhoard.com/avatars/...",
      "bio": "Craft beer enthusiast",
      "location": "Portland, OR",
      "created_at": "2023-06-15T10:00:00Z",
      "displayName": "John Doe"
    },
    "stats": {
      "followers": 42,
      "following": 28,
      "collection": 156,
      "ratings": 89
    },
    "social": {
      "isFollowing": false,
      "isOwnProfile": true
    },
    "recentActivity": [
      {
        "id": "uuid",
        "type": "rating",
        "data": {
          "beer_name": "Hop Heaven IPA",
          "brewery_name": "Craft Brewery",
          "rating": 4.5
        },
        "timestamp": "2024-01-14T18:30:00Z"
      }
    ]
  }
}

Example:

JavaScript
const response = await fetch('/api/v1/social?userId=user-uuid-here');
const { data } = await response.json();

console.log(`${data.user.displayName} has ${data.stats.followers} followers`);

Activity Feed

Get Activity Feed

GET /api/v1/social?action=feed

Get the activity feed showing recent actions from users you follow.

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | action | string | Required: feed | | limit | number | Results per page (default: 20) | | offset | number | Pagination offset (default: 0) |

Response:

JSON
{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "eventType": "collection_add",
      "targetType": "beer",
      "targetId": "beer-uuid",
      "metadata": {
        "beerName": "Imperial Stout",
        "breweryName": "Dark Brewing Co",
        "quantity": 6
      },
      "createdAt": "2024-01-15T14:30:00Z",
      "user": {
        "id": "uuid",
        "username": "craftlover",
        "avatarUrl": "https://cdn.brewhoard.com/avatars/...",
        "displayName": "Sarah Smith"
      }
    },
    {
      "id": "uuid",
      "eventType": "rating",
      "targetType": "beer",
      "targetId": "beer-uuid",
      "metadata": {
        "beerName": "Hoppy Blonde",
        "rating": 4.5,
        "review": "Excellent summer beer!"
      },
      "createdAt": "2024-01-15T12:15:00Z",
      "user": {
        "id": "uuid",
        "username": "hophead",
        "displayName": "Mike Johnson"
      }
    }
  ]
}

Activity Event Types

Event TypeDescriptionMetadata
collection_addAdded beer to collectionbeerName, breweryName, quantity
collection_removeRemoved beer from collectionbeerName
ratingRated a beerbeerName, rating, review
listing_createCreated marketplace listingbeerName, price
listing_soldSold a listingbeerName, price
followFollowed a userfollowingId, followingUsername
consumeConsumed a beerbeerName, quantity
likeLiked contenttargetType, targetId
commentCommented on contenttargetType, content

Example:

JavaScript
async function loadFeed(page = 1) {
  const limit = 20;
  const offset = (page - 1) * limit;
  
  const response = await fetch(
    `/api/v1/social?action=feed&limit=${limit}&offset=${offset}`
  );
  
  const { data } = await response.json();
  return data;
}

Likes

Toggle Like

POST /api/v1/social/like

Like or unlike content (ratings, collection items, listings).

Request:

JSON
{
  "targetType": "rating",
  "targetId": "uuid"
}

Target Types: | Type | Description | |------|-------------| | rating | A beer rating/review | | collection_item | A collection entry | | listing | A marketplace listing |

Response:

JSON
{
  "success": true,
  "data": {
    "liked": true
  }
}

The response liked field indicates the new state:

  • true - Content is now liked
  • false - Content is now unliked

Example:

JavaScript
async function toggleLike(targetType, targetId) {
  const response = await fetch('/api/v1/social/like', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ targetType, targetId }),
  });
  
  const { data } = await response.json();
  return data.liked;
}

// Usage
const isLiked = await toggleLike('rating', 'rating-uuid');

Comments

Get Comments

GET /api/v1/social/comments

Get comments for a piece of content.

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | targetType | string | Content type | | targetId | uuid | Content ID | | limit | number | Results per page (default: 50) | | offset | number | Pagination offset |

Response:

JSON
{
  "success": true,
  "data": {
    "comments": [
      {
        "id": "uuid",
        "content": "Great choice! I love this beer too.",
        "parentId": null,
        "createdAt": "2024-01-15T10:30:00Z",
        "updatedAt": "2024-01-15T10:30:00Z",
        "user": {
          "id": "uuid",
          "username": "beerfan",
          "avatarUrl": "https://cdn.brewhoard.com/avatars/...",
          "displayName": "Alex Brown"
        }
      }
    ]
  }
}

Add Comment

POST /api/v1/social/comments

Add a comment to content.

Request:

JSON
{
  "targetType": "rating",
  "targetId": "uuid",
  "content": "Excellent review! I had the same experience.",
  "parentId": null
}
FieldTypeRequiredDescription
targetTypestringYesType of content
targetIduuidYesID of content
contentstringYesComment text
parentIduuidNoParent comment ID for replies

Response:

JSON
{
  "success": true,
  "data": {
    "comment": {
      "id": "uuid",
      "content": "Excellent review! I had the same experience.",
      "parentId": null,
      "createdAt": "2024-01-15T14:30:00Z",
      "user": {
        "id": "uuid",
        "username": "currentuser",
        "displayName": "Current User"
      }
    }
  }
}

Followers & Following

Get User Followers

GET /api/v1/social/followers

Get list of users following a specific user.

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | userId | uuid | Required: User ID | | limit | number | Results per page (default: 20) | | offset | number | Pagination offset |

Response:

JSON
{
  "success": true,
  "data": {
    "followers": [
      {
        "id": "uuid",
        "username": "craftlover",
        "first_name": "Sarah",
        "last_name": "Smith",
        "avatar_url": "https://cdn.brewhoard.com/avatars/...",
        "displayName": "Sarah Smith",
        "followed_at": "2024-01-10T08:00:00Z"
      }
    ],
    "pagination": {
      "total": 42,
      "limit": 20,
      "offset": 0,
      "hasMore": true
    }
  }
}

Get User Following

GET /api/v1/social/following

Get list of users that a specific user follows.

Query Parameters: | Parameter | Type | Description | |-----------|------|-------------| | userId | uuid | Required: User ID | | limit | number | Results per page (default: 20) | | offset | number | Pagination offset |

Response:

JSON
{
  "success": true,
  "data": {
    "following": [
      {
        "id": "uuid",
        "username": "hophead",
        "displayName": "Mike Johnson",
        "followed_at": "2024-01-05T12:00:00Z"
      }
    ],
    "pagination": {
      "total": 28,
      "limit": 20,
      "offset": 0,
      "hasMore": true
    }
  }
}

Real-Time Events

Social features emit real-time events via Socket.IO. Subscribe to these events for live updates.

Event Types

EventDescriptionPayload
activity:newNew activity from followed userActivity object
notification:followSomeone followed you{ fromUserId, fromUsername }
notification:likeSomeone liked your content{ fromUser, targetType, targetId }
notification:commentSomeone commented on your content{ fromUser, content }

Client Example

JavaScript
import { io } from 'socket.io-client';

const socket = io();

// Subscribe to activity updates
socket.on('activity:new', (activity) => {
  console.log('New activity:', activity);
  // Add to feed UI
});

// Subscribe to notifications
socket.on('notification:follow', (data) => {
  console.log(`${data.fromUsername} started following you!`);
});

socket.on('notification:like', (data) => {
  console.log(`${data.fromUser} liked your ${data.targetType}`);
});

Error Handling

All endpoints return consistent error responses:

JSON
{
  "success": false,
  "error": "User ID is required",
  "code": "MISSING_USER_ID"
}

Common Error Codes

CodeHTTP StatusDescription
UNAUTHORIZED401Authentication required
MISSING_USER_ID400User ID parameter missing
VALIDATION_ERROR400Invalid request data
NOT_FOUND404User or resource not found
INTERNAL_ERROR500Server error

Usage Examples

Complete Follow Flow

JavaScript
// Check if following
async function checkFollowing(userId) {
  const response = await fetch(`/api/v1/social?userId=${userId}`);
  const { data } = await response.json();
  return data.social.isFollowing;
}

// Toggle follow
async function toggleFollow(userId, isCurrentlyFollowing) {
  const method = isCurrentlyFollowing ? 'DELETE' : 'POST';
  
  const response = await fetch('/api/v1/social', {
    method,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ followingId: userId }),
  });
  
  return response.ok;
}

Building an Activity Feed

Svelte
<script>
  let activities = $state([]);
  let loading = $state(false);
  let page = $state(1);
  
  async function loadMore() {
    loading = true;
    const limit = 20;
    const offset = (page - 1) * limit;
    
    const response = await fetch(
      `/api/v1/social?action=feed&limit=${limit}&offset=${offset}`
    );
    const { data } = await response.json();
    
    activities = [...activities, ...data];
    page += 1;
    loading = false;
  }
  
  // Initial load
  $effect(() => {
    loadMore();
  });
</script>

{#each activities as activity}
  <ActivityCard {activity} />
{/each}

<button onclick={loadMore} disabled={loading}>
  {loading ? 'Loading...' : 'Load More'}
</button>

Next Steps