forked from UKSOURCE/hailearning.edu.vn
feat: Implement blog API service and refactor components for improved data fetching
This commit is contained in:
415
api/blogsApi.ts
Normal file
415
api/blogsApi.ts
Normal file
@@ -0,0 +1,415 @@
|
||||
import {
|
||||
BlogListResponse,
|
||||
BlogDetailResponse,
|
||||
BlogFeaturedResponse,
|
||||
BlogRecentResponse,
|
||||
CategoryListResponse,
|
||||
CategoryDetailResponse,
|
||||
TagListResponse,
|
||||
TagDetailResponse,
|
||||
BlogQueryParams,
|
||||
} from '../types/blog';
|
||||
|
||||
/**
|
||||
* Lấy API URL từ environment variable
|
||||
* Hỗ trợ cả REACT_APP_API_URL và NEXT_PUBLIC_API_URL
|
||||
*/
|
||||
const getApiUrl = (): string => {
|
||||
// Trong Next.js, client-side env vars cần prefix NEXT_PUBLIC_
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
if (!apiUrl) {
|
||||
console.warn('NEXT_PUBLIC_API_URL is not set. Using default http://localhost:3001');
|
||||
return 'http://localhost:3001';
|
||||
}
|
||||
|
||||
return apiUrl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blog list từ API
|
||||
* @param params - Query parameters (page, limit, category, tag, search)
|
||||
* @returns Promise<BlogListResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchBlogList = async (
|
||||
params?: BlogQueryParams
|
||||
): Promise<BlogListResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (params?.page) queryParams.append('page', params.page.toString());
|
||||
if (params?.limit) queryParams.append('limit', params.limit.toString());
|
||||
if (params?.category) queryParams.append('category', params.category);
|
||||
if (params?.tag) queryParams.append('tag', params.tag);
|
||||
if (params?.search) queryParams.append('search', params.search);
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
const url = `${apiUrl}/api/blog${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// Next.js: cache và revalidate (disabled)
|
||||
// next: { revalidate: 60 }, // Revalidate mỗi 60 giây
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching blog list:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch blog list: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blog detail by slug từ API
|
||||
* @param slug - Blog post slug
|
||||
* @returns Promise<BlogDetailResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchBlogDetail = async (
|
||||
slug: string
|
||||
): Promise<BlogDetailResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/${slug}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// No cache for blog detail (disabled caching)
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error('Blog post not found');
|
||||
}
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogDetailResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching blog detail:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch blog detail: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch featured blogs từ API
|
||||
* @param limit - Số lượng blog featured (default: 3)
|
||||
* @returns Promise<BlogFeaturedResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchFeaturedBlogs = async (
|
||||
limit: number = 5
|
||||
): Promise<BlogFeaturedResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/featured?limit=${limit}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// next: { revalidate: 60 },
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogFeaturedResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching featured blogs:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch featured blogs: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch recent blogs từ API
|
||||
* @param limit - Số lượng blog recent (default: 5)
|
||||
* @returns Promise<BlogRecentResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchRecentBlogs = async (
|
||||
limit: number = 5
|
||||
): Promise<BlogRecentResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/recent?limit=${limit}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// next: { revalidate: 60 },
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogRecentResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching recent blogs:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch recent blogs: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch categories list từ API
|
||||
* @returns Promise<CategoryListResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchCategories = async (): Promise<CategoryListResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/categories`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// next: { revalidate: 300 }, // Categories ít thay đổi, cache lâu hơn
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: CategoryListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch categories: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch category detail by slug từ API
|
||||
* @param slug - Category slug
|
||||
* @returns Promise<CategoryDetailResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchCategoryDetail = async (
|
||||
slug: string
|
||||
): Promise<CategoryDetailResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/categories/${slug}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// next: { revalidate: 300 },
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error('Category not found');
|
||||
}
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: CategoryDetailResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching category detail:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch category detail: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch tags list từ API
|
||||
* @returns Promise<TagListResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchTags = async (): Promise<TagListResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/tags`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// next: { revalidate: 300 },
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: TagListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching tags:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch tags: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch popular tags từ API
|
||||
* @param limit - Số lượng tags (default: 10)
|
||||
* @returns Promise<TagListResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchPopularTags = async (
|
||||
limit: number = 10
|
||||
): Promise<TagListResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/tags/popular?limit=${limit}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// next: { revalidate: 300 },
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: TagListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching popular tags:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch popular tags: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch tag detail by slug từ API
|
||||
* @param slug - Tag slug
|
||||
* @returns Promise<TagDetailResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchTagDetail = async (
|
||||
slug: string
|
||||
): Promise<TagDetailResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const url = `${apiUrl}/api/blog/tags/${slug}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// next: { revalidate: 300 },
|
||||
// no-cache
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error('Tag not found');
|
||||
}
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: TagDetailResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching tag detail:', error);
|
||||
throw new Error(
|
||||
`Failed to fetch tag detail: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blogs by category từ API
|
||||
* @param categorySlug - Category slug
|
||||
* @param params - Query parameters (page, limit)
|
||||
* @returns Promise<BlogListResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchBlogsByCategory = async (
|
||||
categorySlug: string,
|
||||
params?: Omit<BlogQueryParams, 'category'>
|
||||
): Promise<BlogListResponse> => {
|
||||
// Lấy category name từ slug
|
||||
const categoryResponse = await fetchCategoryDetail(categorySlug);
|
||||
const categoryName = categoryResponse.data.name;
|
||||
|
||||
return fetchBlogList({
|
||||
...params,
|
||||
category: categoryName,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blogs by tag từ API
|
||||
* @param tagSlug - Tag slug
|
||||
* @param params - Query parameters (page, limit)
|
||||
* @returns Promise<BlogListResponse>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchBlogsByTag = async (
|
||||
tagSlug: string,
|
||||
params?: Omit<BlogQueryParams, 'tag'>
|
||||
): Promise<BlogListResponse> => {
|
||||
// Lấy tag name từ slug
|
||||
const tagResponse = await fetchTagDetail(tagSlug);
|
||||
const tagName = tagResponse.data.name;
|
||||
|
||||
return fetchBlogList({
|
||||
...params,
|
||||
tag: tagName,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user