Comments API
Overviewβ
The Comments API provides comprehensive commenting functionality for Ring Platform content including news articles, entities, and opportunities. It supports nested comments (replies), CRUD operations, and real-time interactions.
Endpointsβ
Get Commentsβ
GET /api/comments
Create Commentβ
POST /api/comments
Update Commentβ
PUT /api/comments
Delete Commentβ
DELETE /api/comments
Authenticationβ
Required: Yes - User must be authenticated via NextAuth session for all operations
GET /api/commentsβ
Retrieve comments for a specific target with filtering and pagination support.
Query Parametersβ
Parameter | Type | Required | Description |
---|---|---|---|
targetType | string | Yes | Type of target: 'news' , 'entity' , 'opportunity' , or 'comment' |
targetId | string | Yes | ID of the target item |
page | number | No | Page number for pagination (default: 1) |
limit | number | No | Number of comments per page (default: 10, max: 50) |
sortBy | string | No | Sort field: 'createdAt' or 'updatedAt' (default: 'createdAt') |
sortOrder | string | No | Sort order: 'asc' or 'desc' (default: 'desc') |
level | number | No | Comment nesting level filter (0-3) |
Request Exampleβ
curl "https://ring.ck.ua/api/comments?targetType=news&targetId=news123&page=1&limit=20" \
-H "Cookie: next-auth.session-token=..."
Response Exampleβ
{
"success": true,
"data": {
"comments": [
{
"id": "comment123",
"content": "Great article! Really insightful.",
"authorId": "user456",
"authorName": "John Doe",
"authorAvatar": "https://example.com/avatar.jpg",
"targetType": "news",
"targetId": "news123",
"parentId": null,
"level": 0,
"replyCount": 2,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z",
"replies": [
{
"id": "comment124",
"content": "I agree completely!",
"authorId": "user789",
"authorName": "Jane Smith",
"authorAvatar": "https://example.com/avatar2.jpg",
"targetType": "comment",
"targetId": "comment123",
"parentId": "comment123",
"level": 1,
"replyCount": 0,
"createdAt": "2024-01-15T11:00:00Z",
"updatedAt": "2024-01-15T11:00:00Z"
}
]
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45,
"totalPages": 3,
"hasNext": true,
"hasPrev": false
},
"stats": {
"totalComments": 45,
"totalTopLevel": 30,
"totalReplies": 15
}
}
}
POST /api/commentsβ
Create a new comment or reply to an existing comment.
Request Bodyβ
Field | Type | Required | Description |
---|---|---|---|
content | string | Yes | Comment content (1-2000 characters) |
targetType | string | Yes | Type: 'news' , 'entity' , 'opportunity' , or 'comment' |
targetId | string | Yes | ID of the target item |
parentId | string | No | ID of parent comment (for replies) |
Request Exampleβ
curl -X POST https://ring.ck.ua/api/comments \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=..." \
-d '{
"content": "This is a great point! Thanks for sharing.",
"targetType": "news",
"targetId": "news123",
"parentId": "comment456"
}'
Response Exampleβ
{
"success": true,
"message": "Comment created successfully",
"data": {
"id": "comment789",
"content": "This is a great point! Thanks for sharing.",
"authorId": "user123",
"authorName": "Alice Johnson",
"authorAvatar": "https://example.com/avatar3.jpg",
"targetType": "comment",
"targetId": "comment456",
"parentId": "comment456",
"level": 2,
"replyCount": 0,
"createdAt": "2024-01-15T12:00:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
}
PUT /api/commentsβ
Update an existing comment (only by the original author).
Request Bodyβ
Field | Type | Required | Description |
---|---|---|---|
id | string | Yes | Comment ID to update |
content | string | Yes | Updated comment content (1-2000 characters) |
Request Exampleβ
curl -X PUT https://ring.ck.ua/api/comments \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=..." \
-d '{
"id": "comment789",
"content": "This is an updated comment with better wording."
}'
Response Exampleβ
{
"success": true,
"message": "Comment updated successfully",
"data": {
"id": "comment789",
"content": "This is an updated comment with better wording.",
"updatedAt": "2024-01-15T12:30:00Z"
}
}
DELETE /api/commentsβ
Delete a comment (only by the original author or admin).
Query Parametersβ
Parameter | Type | Required | Description |
---|---|---|---|
id | string | Yes | Comment ID to delete |
Request Exampleβ
curl -X DELETE "https://ring.ck.ua/api/comments?id=comment789" \
-H "Cookie: next-auth.session-token=..."
Response Exampleβ
{
"success": true,
"message": "Comment deleted successfully"
}
Error Responsesβ
400 Bad Requestβ
{
"success": false,
"error": "Content is required and must be between 1-2000 characters"
}
401 Unauthorizedβ
{
"success": false,
"error": "Authentication required"
}
403 Forbiddenβ
{
"success": false,
"error": "You can only edit your own comments"
}
404 Not Foundβ
{
"success": false,
"error": "Comment not found"
}
409 Conflictβ
{
"success": false,
"error": "Maximum nesting level (3) exceeded"
}
Database Structureβ
Comments Collection (Firebase Firestore)β
interface Comment {
id: string; // Auto-generated document ID
content: string; // Comment text (1-2000 chars)
authorId: string; // ID of comment author
authorName: string; // Author's display name
authorAvatar?: string; // Author's avatar URL
targetType: 'news' | 'entity' | 'opportunity' | 'comment';
targetId: string; // ID of target item
parentId?: string; // ID of parent comment (for replies)
level: number; // Nesting level (0-3)
replyCount: number; // Number of direct replies
createdAt: Timestamp; // Creation timestamp
updatedAt: Timestamp; // Last update timestamp
isDeleted?: boolean; // Soft delete flag
}
Target Collections (Updated with comment counts)β
// News, entities, and opportunities get updated comment counts
interface TargetWithComments {
id: string;
// ... other fields
commentCount: number; // Total number of comments
updatedAt: Timestamp;
}
Business Logicβ
Comment Nesting Rulesβ
- Maximum nesting level: 3 levels deep
- Level 0: Top-level comments on content
- Level 1-3: Replies to comments
- Reply structure: Each reply increases level by 1
Comment Managementβ
- Creation: Automatically increments target's comment count
- Deletion: Soft delete to preserve reply structure
- Updates: Only by original author within reasonable time
- Moderation: Admin users can delete any comment
Atomic Operationsβ
- Uses Firebase transactions for consistent comment counts
- Prevents race conditions during concurrent operations
- Maintains data integrity across related collections
Frontend Integrationβ
React 19 with useActionStateβ
import { useActionState, useOptimistic } from 'react';
import { createComment } from '@/app/actions/comments';
function CommentForm({ targetType, targetId, parentId = null, onSuccess }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
[],
(state, newComment) => [...state, { ...newComment, id: 'temp-' + Date.now() }]
);
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const content = formData.get('content') as string;
// Add optimistic comment
addOptimisticComment({
content,
authorName: 'You',
createdAt: new Date().toISOString(),
level: parentId ? 1 : 0
});
const result = await createComment({
content,
targetType,
targetId,
parentId
});
if (result.success) {
onSuccess?.(result.data);
}
return result;
},
{ success: false }
);
return (
<form action={formAction}>
<textarea
name="content"
placeholder="Write a comment..."
maxLength={2000}
required
/>
<button type="submit" disabled={isPending}>
{isPending ? 'Posting...' : 'Post Comment'}
</button>
{!state.success && state.error && (
<p className="error">{state.error}</p>
)}
</form>
);
}
Server Actionsβ
// app/actions/comments.ts
export async function createComment(data: CommentFormData) {
const response = await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json();
}
export async function editComment(id: string, content: string) {
const response = await fetch('/api/comments', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, content })
});
return response.json();
}
export async function deleteComment(id: string) {
const response = await fetch(`/api/comments?id=${id}`, {
method: 'DELETE'
});
return response.json();
}
Security Considerationsβ
- Authentication: All operations require valid NextAuth session
- Authorization: Users can only edit/delete their own comments
- Content Validation: Strict input validation and sanitization
- Rate Limiting: Implement rate limiting for comment creation
- Spam Prevention: Content filtering and user reputation system
- XSS Prevention: Content sanitization before storage and display
Performance Optimizationsβ
- Pagination: Efficient pagination for large comment threads
- Indexing: Optimized database indexes for common queries
- Caching: Consider caching frequently accessed comment threads
- Lazy Loading: Load replies on demand to improve initial page load
- Optimistic Updates: Immediate UI feedback while processing
Testingβ
See the Comments Testing Notebook for interactive testing examples and comprehensive test scenarios.
Comment Likes Feature β€οΈβ
Comments are now fully likable! Each comment includes:
- Like count: Real-time tracking of likes per comment
- User like status: Track which comments each user has liked
- Optimistic UI: Instant feedback with React 19 useOptimistic
- Analytics: Track engagement with comment interactions
Comment Likes APIβ
Method | Endpoint | Description |
---|---|---|
POST | /api/comments/[id]/like | Like/unlike a comment (toggles) |
GET | /api/comments/[id]/like | Get like status for current user |
Database Schema for Likesβ
Updated Comment Documentβ
{
"id": "comment123",
"content": "Great article! Very informative.",
"authorId": "user456",
"authorName": "John Doe",
"authorAvatar": "https://example.com/avatar.jpg",
"targetId": "news789",
"targetType": "news",
"parentId": null,
"level": 0,
"likes": 15,
"replies": 2,
"status": "active",
"isEdited": false,
"isPinned": false,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z",
"editedAt": null
}
Comment Like Documentβ
{
"id": "comment123_user456",
"commentId": "comment123",
"userId": "user456",
"userName": "John Doe",
"userAvatar": "https://example.com/avatar.jpg",
"createdAt": "2024-01-15T14:22:00Z"
}
Implementation Exampleβ
import { useState } from 'react'
function CommentWithLikes({ comment, currentUser }) {
const [liked, setLiked] = useState(comment.userLiked)
const [likes, setLikes] = useState(comment.likes)
const [loading, setLoading] = useState(false)
const toggleLike = async () => {
// Optimistic update
setLiked(!liked)
setLikes(liked ? likes - 1 : likes + 1)
setLoading(true)
try {
const response = await fetch(`/api/comments/${comment.id}/like`, {
method: 'POST'
})
const result = await response.json()
setLiked(result.data.liked)
setLikes(result.data.likes)
} catch (error) {
// Revert on error
setLiked(liked)
setLikes(comment.likes)
} finally {
setLoading(false)
}
}
return (
<div className="comment">
<p>{comment.content}</p>
<div className="comment-actions">
<button
onClick={toggleLike}
disabled={loading}
className={liked ? 'liked' : ''}
>
{liked ? 'β€οΈ' : 'π€'} {likes}
</button>
<span>Reply ({comment.replies})</span>
</div>
</div>
)
}
Related Endpointsβ
- Comment Likes API - For liking individual comments
- News Likes API - For liking news articles
- News API - For news article management
- Notifications API - For comment notifications
Usage Examplesβ
Basic Comment Threadβ
// Get comments for a news article
const response = await fetch('/api/comments?targetType=news&targetId=news123');
const { data } = await response.json();
// Display nested comments
function CommentThread({ comments }) {
return (
<div className="comment-thread">
{comments.map(comment => (
<div key={comment.id} className={`comment level-${comment.level}`}>
<div className="comment-content">
<strong>{comment.authorName}</strong>
<p>{comment.content}</p>
<small>{new Date(comment.createdAt).toLocaleDateString()}</small>
</div>
{comment.replies && comment.replies.length > 0 && (
<div className="replies">
<CommentThread comments={comment.replies} />
</div>
)}
</div>
))}
</div>
);
}
Changelogβ
v1.0.0 (Current)β
- Initial implementation with full CRUD operations
- Nested commenting system (3 levels deep)
- React 19 optimistic updates support
- Firebase Firestore integration
- Comprehensive filtering and pagination
- Real-time comment counts
- Soft delete functionality