830 lines
21 KiB
TypeScript
830 lines
21 KiB
TypeScript
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1';
|
|
|
|
// Debug logging
|
|
console.log('API_BASE_URL:', API_BASE_URL);
|
|
console.log('VITE_API_URL env:', import.meta.env.VITE_API_URL);
|
|
|
|
// Helper function to get auth headers
|
|
function getAuthHeaders(): HeadersInit {
|
|
const token = localStorage.getItem('token');
|
|
const headers: HeadersInit = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
// Enhanced fetch wrapper
|
|
async function authFetch(url: string, options: RequestInit = {}): Promise<Response> {
|
|
const headers = getAuthHeaders();
|
|
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers: {
|
|
...headers,
|
|
...options.headers,
|
|
},
|
|
});
|
|
|
|
// Handle 401 unauthorized
|
|
if (response.status === 401) {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('user');
|
|
window.location.href = '/auth';
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
export interface Article {
|
|
id: string;
|
|
title: string;
|
|
content: string;
|
|
excerpt: string | null;
|
|
slug: string;
|
|
featuredImage: string;
|
|
imagePosition: 'top' | 'left' | 'right' | 'none';
|
|
imageSize: 'small' | 'medium' | 'large';
|
|
videoUrl: string;
|
|
videoPosition: 'top' | 'inline' | 'bottom' | 'none';
|
|
videoCaption: string;
|
|
tags: string[];
|
|
status: 'draft' | 'published' | 'archived';
|
|
views: number;
|
|
strapiId: string | null;
|
|
authorId: string | null;
|
|
categoryId: string | null;
|
|
author?: {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
bio: string | null;
|
|
avatar: string | null;
|
|
};
|
|
category?: {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
description: string | null;
|
|
};
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
isHero?: boolean;
|
|
isPinned?: boolean;
|
|
facebookShares?: number;
|
|
twitterShares?: number;
|
|
whatsappShares?: number;
|
|
telegramShares?: number;
|
|
ogTitle?: string;
|
|
ogDescription?: string;
|
|
ogImage?: string;
|
|
twitterTitle?: string;
|
|
twitterDescription?: string;
|
|
twitterImage?: string;
|
|
}
|
|
|
|
export interface ArticlesResponse {
|
|
data: Article[];
|
|
total: number;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface FindArticlesParams {
|
|
category?: string;
|
|
author?: string;
|
|
tag?: string;
|
|
status?: string;
|
|
search?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface CreateArticleDto {
|
|
title: string;
|
|
content: string;
|
|
excerpt?: string;
|
|
slug?: string;
|
|
featuredImage?: string;
|
|
tags?: string[];
|
|
status?: 'draft' | 'published' | 'archived';
|
|
strapiId?: string;
|
|
authorId?: string;
|
|
categoryId?: string;
|
|
imagePosition?: 'top' | 'left' | 'right' | 'none';
|
|
imageSize?: 'small' | 'medium' | 'large';
|
|
videoUrl?: string;
|
|
videoPosition?: 'top' | 'inline' | 'bottom' | 'none';
|
|
videoCaption?: string;
|
|
}
|
|
|
|
export interface UpdateArticleDto {
|
|
title?: string;
|
|
content?: string;
|
|
excerpt?: string;
|
|
slug?: string;
|
|
featuredImage?: string;
|
|
tags?: string[];
|
|
status?: 'draft' | 'published' | 'archived';
|
|
strapiId?: string;
|
|
authorId?: string;
|
|
categoryId?: string;
|
|
imagePosition?: 'top' | 'left' | 'right' | 'none';
|
|
imageSize?: 'small' | 'medium' | 'large';
|
|
videoUrl?: string;
|
|
videoPosition?: 'top' | 'inline' | 'bottom' | 'none';
|
|
videoCaption?: string;
|
|
isHero?: boolean;
|
|
}
|
|
|
|
export async function fetchArticles(params: FindArticlesParams = {}): Promise<ArticlesResponse> {
|
|
console.log('fetchArticles called with params:', params, 'API_BASE_URL:', API_BASE_URL);
|
|
const searchParams = new URLSearchParams();
|
|
|
|
// Convert parameters to proper types for URLSearchParams
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null) {
|
|
if (typeof value === 'number') {
|
|
searchParams.append(key, value.toString());
|
|
} else {
|
|
searchParams.append(key, String(value));
|
|
}
|
|
}
|
|
});
|
|
|
|
const url = `${API_BASE_URL}/articles?${searchParams}`;
|
|
console.log('Fetching from:', url);
|
|
|
|
const response = await authFetch(url);
|
|
console.log('Response status:', response.status, 'ok:', response.ok);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch articles');
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('Response data:', data);
|
|
return data;
|
|
}
|
|
|
|
export async function fetchArticleBySlug(slug: string): Promise<Article> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles/slug/${slug}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch article');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchArticleById(id: string): Promise<Article> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles/${id}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch article');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function createArticle(dto: CreateArticleDto): Promise<Article> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to create article');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function updateArticle(id: string, dto: UpdateArticleDto): Promise<Article> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update article');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function deleteArticle(id: string): Promise<void> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete article');
|
|
}
|
|
}
|
|
|
|
export async function archiveArticle(id: string): Promise<Article> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles/${id}/archive`, {
|
|
method: 'PATCH',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to archive article');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function publishArticle(id: string, status: 'draft' | 'published' = 'published'): Promise<Article> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles/${id}/publish?status=${status}`, {
|
|
method: 'PATCH',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to publish article');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
// Live Blog Types
|
|
export interface LiveBlogUpdate {
|
|
id: string;
|
|
content: string;
|
|
isPinned: boolean;
|
|
authorId: string | null;
|
|
scheduledAt: string | null;
|
|
strapiId: string | null;
|
|
author?: {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
bio: string | null;
|
|
avatar: string | null;
|
|
};
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface LiveBlog {
|
|
id: string;
|
|
title: string;
|
|
slug: string;
|
|
description: string | null;
|
|
status: 'draft' | 'live' | 'ended' | 'archived';
|
|
isPinned: boolean;
|
|
strapiId: string | null;
|
|
authorId: string | null;
|
|
categoryId: string | null;
|
|
featuredImage: string;
|
|
imagePosition: 'top' | 'left' | 'right' | 'none';
|
|
imageSize: 'small' | 'medium' | 'large';
|
|
videoUrl: string;
|
|
videoPosition: 'top' | 'inline' | 'bottom' | 'none';
|
|
videoCaption: string;
|
|
viewCount: number;
|
|
author?: {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
bio: string | null;
|
|
avatar: string | null;
|
|
};
|
|
category?: {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
description: string | null;
|
|
};
|
|
updates?: LiveBlogUpdate[];
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface LiveBlogsResponse {
|
|
data: LiveBlog[];
|
|
total: number;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface LiveBlogUpdatesResponse {
|
|
data: LiveBlogUpdate[];
|
|
total: number;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface FindLiveBlogsParams {
|
|
category?: string;
|
|
author?: string;
|
|
status?: string;
|
|
search?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface CreateLiveBlogUpdateDto {
|
|
content: string;
|
|
isPinned?: boolean;
|
|
authorId?: string;
|
|
scheduledAt?: string;
|
|
strapiId?: string;
|
|
}
|
|
|
|
export interface UpdateLiveBlogUpdateDto {
|
|
content?: string;
|
|
isPinned?: boolean;
|
|
authorId?: string;
|
|
scheduledAt?: string;
|
|
strapiId?: string;
|
|
}
|
|
|
|
export interface CreateLiveBlogDto {
|
|
title: string;
|
|
slug?: string;
|
|
description?: string;
|
|
status?: 'draft' | 'live' | 'ended' | 'archived';
|
|
authorId?: string;
|
|
categoryId?: string;
|
|
strapiId?: string;
|
|
}
|
|
|
|
export interface UpdateLiveBlogDto {
|
|
title?: string;
|
|
slug?: string;
|
|
description?: string;
|
|
status?: 'draft' | 'live' | 'ended' | 'archived';
|
|
isPinned?: boolean;
|
|
authorId?: string;
|
|
categoryId?: string;
|
|
strapiId?: string;
|
|
}
|
|
|
|
// Live Blog API Functions
|
|
export async function fetchLiveBlogs(params: FindLiveBlogsParams = {}): Promise<LiveBlogsResponse> {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null) {
|
|
if (typeof value === 'number') {
|
|
searchParams.append(key, value.toString());
|
|
} else {
|
|
searchParams.append(key, String(value));
|
|
}
|
|
}
|
|
});
|
|
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs?${searchParams}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch live blogs');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchLiveBlogBySlug(slugOrId: string): Promise<LiveBlog> {
|
|
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(slugOrId);
|
|
const endpoint = isUuid
|
|
? `${API_BASE_URL}/live-blogs/${slugOrId}`
|
|
: `${API_BASE_URL}/live-blogs/slug/${slugOrId}`;
|
|
const response = await authFetch(endpoint);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch live blog');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchLiveBlogById(id: string): Promise<LiveBlog> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${id}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch live blog');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchLiveBlogUpdates(
|
|
liveBlogId: string,
|
|
page = 1,
|
|
limit = 50
|
|
): Promise<LiveBlogUpdatesResponse> {
|
|
const response = await authFetch(
|
|
`${API_BASE_URL}/live-blogs/${liveBlogId}/updates?page=${page}&limit=${limit}`
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch live blog updates');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchRecentLiveBlogs(): Promise<LiveBlog[]> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/recent`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch recent live blogs');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchHeroArticle(): Promise<Article | null> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles/hero`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch hero article');
|
|
}
|
|
const article = await response.json();
|
|
return article || null;
|
|
}
|
|
|
|
export async function fetchLatestArticles(limit = 12): Promise<ArticlesResponse> {
|
|
const response = await authFetch(`${API_BASE_URL}/articles?status=published&limit=${limit}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch latest articles');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchPinnedLiveBlogs(): Promise<LiveBlog[]> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/featured`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch pinned live blogs');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchActiveLiveBlogs(): Promise<LiveBlog[]> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/active`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch active live blogs');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
// Admin functions
|
|
export async function createLiveBlogUpdate(
|
|
liveBlogId: string,
|
|
dto: CreateLiveBlogUpdateDto
|
|
): Promise<LiveBlogUpdate> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${liveBlogId}/updates`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to create live blog update');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function updateLiveBlogUpdate(
|
|
liveBlogId: string,
|
|
updateId: string,
|
|
dto: UpdateLiveBlogUpdateDto
|
|
): Promise<LiveBlogUpdate> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${liveBlogId}/updates/${updateId}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update live blog update');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function deleteLiveBlogUpdate(liveBlogId: string, updateId: string): Promise<void> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${liveBlogId}/updates/${updateId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete live blog update');
|
|
}
|
|
}
|
|
|
|
export async function createLiveBlog(dto: CreateLiveBlogDto): Promise<LiveBlog> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to create live blog');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function updateLiveBlog(id: string, dto: UpdateLiveBlogDto): Promise<LiveBlog> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update live blog');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function deleteLiveBlog(id: string): Promise<void> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete live blog');
|
|
}
|
|
}
|
|
|
|
export async function archiveLiveBlog(id: string): Promise<LiveBlog> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${id}/archive`, {
|
|
method: 'PATCH',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to archive live blog');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function publishLiveBlog(id: string, status: 'draft' | 'live' | 'ended' = 'draft'): Promise<LiveBlog> {
|
|
const response = await authFetch(`${API_BASE_URL}/live-blogs/${id}/publish?status=${status}`, {
|
|
method: 'PATCH',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to publish live blog');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
// Auth Types
|
|
import type { User, LoginDto, RegisterDto, AuthResponse } from '@/types';
|
|
|
|
// Auth API Functions
|
|
export async function login(dto: LoginDto): Promise<AuthResponse> {
|
|
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(dto),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.message || 'Login failed');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
export async function register(dto: RegisterDto): Promise<AuthResponse> {
|
|
const response = await fetch(`${API_BASE_URL}/auth/register`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(dto),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.message || 'Registration failed');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
export async function getProfile(): Promise<User> {
|
|
const response = await authFetch(`${API_BASE_URL}/users/profile`, {
|
|
method: 'GET',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch profile');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
export async function logout(): Promise<void> {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('user');
|
|
}
|
|
|
|
// Comment Types
|
|
export interface Comment {
|
|
id: string;
|
|
content: string;
|
|
articleId: string | null;
|
|
liveBlogId: string | null;
|
|
parentCommentId: string | null;
|
|
userId: string;
|
|
isVisible: boolean;
|
|
user?: {
|
|
id: string;
|
|
username: string;
|
|
email: string;
|
|
role: string;
|
|
};
|
|
reactions?: {
|
|
likes: number;
|
|
dislikes: number;
|
|
};
|
|
replies?: Comment[];
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface CreateCommentDto {
|
|
content: string;
|
|
articleId?: string;
|
|
liveBlogId?: string;
|
|
parentCommentId?: string;
|
|
}
|
|
|
|
export interface UpdateCommentDto {
|
|
content?: string;
|
|
isVisible?: boolean;
|
|
}
|
|
|
|
export interface FindCommentsParams {
|
|
articleId?: string;
|
|
liveBlogId?: string;
|
|
parentCommentId?: string;
|
|
parentId?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface CommentsResponse {
|
|
data: Comment[];
|
|
total: number;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface ReactionCounts {
|
|
likes: number;
|
|
dislikes: number;
|
|
}
|
|
|
|
export interface CreateReactionDto {
|
|
type: 'like' | 'dislike';
|
|
articleId?: string;
|
|
liveBlogId?: string;
|
|
commentId?: string;
|
|
}
|
|
|
|
// Comment API Functions
|
|
// Interface for backend comment response
|
|
interface BackendComment {
|
|
id: string;
|
|
content: string;
|
|
articleId: string | null;
|
|
liveBlogId: string | null;
|
|
parentId: string | null;
|
|
userId: string;
|
|
likeCount: number;
|
|
dislikeCount: number;
|
|
isVisible: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
user?: {
|
|
id: string;
|
|
username: string;
|
|
};
|
|
replies?: BackendComment[];
|
|
}
|
|
|
|
// Recursive function to map comment and its replies
|
|
function mapBackendComment(comment: BackendComment): Comment {
|
|
const mappedComment: Comment = {
|
|
id: comment.id,
|
|
content: comment.content,
|
|
articleId: comment.articleId,
|
|
liveBlogId: comment.liveBlogId,
|
|
parentCommentId: comment.parentId,
|
|
userId: comment.userId,
|
|
isVisible: comment.isVisible,
|
|
reactions: {
|
|
likes: comment.likeCount || 0,
|
|
dislikes: comment.dislikeCount || 0,
|
|
},
|
|
replies: comment.replies?.map(mapBackendComment) || [],
|
|
createdAt: comment.createdAt,
|
|
updatedAt: comment.updatedAt,
|
|
};
|
|
return mappedComment;
|
|
}
|
|
|
|
export async function fetchComments(params: FindCommentsParams = {}): Promise<CommentsResponse> {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
// Map parentCommentId to parentId for backend compatibility
|
|
const backendParams = { ...params };
|
|
if (backendParams.parentCommentId) {
|
|
backendParams.parentId = backendParams.parentCommentId;
|
|
delete backendParams.parentCommentId;
|
|
}
|
|
|
|
Object.entries(backendParams).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null) {
|
|
if (typeof value === 'number') {
|
|
searchParams.append(key, value.toString());
|
|
} else {
|
|
searchParams.append(key, String(value));
|
|
}
|
|
}
|
|
});
|
|
|
|
const response = await authFetch(`${API_BASE_URL}/comments?${searchParams}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch comments');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
const mappedData = (data as BackendComment[]).map(mapBackendComment);
|
|
|
|
return {
|
|
data: mappedData,
|
|
total: mappedData.length,
|
|
};
|
|
}
|
|
|
|
export async function createComment(dto: CreateCommentDto): Promise<Comment> {
|
|
// Map parentCommentId to parentId for backend compatibility
|
|
const backendDto = {
|
|
content: dto.content,
|
|
articleId: dto.articleId,
|
|
liveBlogId: dto.liveBlogId,
|
|
parentId: dto.parentCommentId,
|
|
};
|
|
|
|
const response = await authFetch(`${API_BASE_URL}/comments`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(backendDto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to create comment');
|
|
}
|
|
|
|
const comment = await response.json() as BackendComment;
|
|
|
|
// Map backend response to frontend interface
|
|
return mapBackendComment(comment);
|
|
}
|
|
|
|
export async function updateComment(id: string, dto: UpdateCommentDto): Promise<Comment> {
|
|
const response = await authFetch(`${API_BASE_URL}/comments/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update comment');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function deleteComment(id: string): Promise<void> {
|
|
const response = await authFetch(`${API_BASE_URL}/comments/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete comment');
|
|
}
|
|
}
|
|
|
|
export async function addReaction(dto: CreateReactionDto): Promise<void> {
|
|
const response = await authFetch(`${API_BASE_URL}/comments/reactions`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(dto),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Failed to add reaction');
|
|
}
|
|
}
|
|
|
|
export async function getReactionCounts(
|
|
articleId?: string,
|
|
liveBlogId?: string,
|
|
commentId?: string
|
|
): Promise<ReactionCounts> {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (articleId) searchParams.append('articleId', articleId);
|
|
if (liveBlogId) searchParams.append('liveBlogId', liveBlogId);
|
|
if (commentId) searchParams.append('commentId', commentId);
|
|
|
|
const url = `${API_BASE_URL}/comments/reactions/counts${searchParams.toString() ? `?${searchParams}` : ''}`;
|
|
const response = await authFetch(url);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch reaction counts');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
export async function getUserReaction(
|
|
articleId?: string,
|
|
liveBlogId?: string,
|
|
commentId?: string
|
|
): Promise<{ type: string | null }> {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (articleId) searchParams.append('articleId', articleId);
|
|
if (liveBlogId) searchParams.append('liveBlogId', liveBlogId);
|
|
if (commentId) searchParams.append('commentId', commentId);
|
|
|
|
const url = `${API_BASE_URL}/comments/reactions/user${searchParams.toString() ? `?${searchParams}` : ''}`;
|
|
const response = await authFetch(url);
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
return { type: null };
|
|
}
|
|
throw new Error('Failed to fetch user reaction');
|
|
}
|
|
return response.json();
|
|
}
|