update news

This commit is contained in:
Phuoc Nguyen
2025-11-10 15:37:55 +07:00
parent 36bdf6613b
commit 67fd5ed142
17 changed files with 1016 additions and 211 deletions

View File

@@ -8,6 +8,7 @@ import 'package:worker/core/constants/api_constants.dart';
import 'package:worker/core/network/dio_client.dart';
import 'package:worker/core/services/frappe_auth_service.dart';
import 'package:worker/features/news/data/models/blog_category_model.dart';
import 'package:worker/features/news/data/models/blog_post_model.dart';
/// News Remote Data Source
///
@@ -90,4 +91,173 @@ class NewsRemoteDataSource {
throw Exception('Unexpected error fetching blog categories: $e');
}
}
/// Get blog posts
///
/// Fetches all published blog posts from Frappe.
/// Returns a list of [BlogPostModel].
///
/// API endpoint: POST https://land.dbiz.com/api/method/frappe.client.get_list
/// Request body:
/// ```json
/// {
/// "doctype": "Blog Post",
/// "fields": ["name","title","published_on","blogger","blog_intro","content","meta_image","meta_description","blog_category"],
/// "filters": {"published":1},
/// "order_by": "published_on desc",
/// "limit_page_length": 0
/// }
/// ```
///
/// Response format:
/// ```json
/// {
/// "message": [
/// {
/// "name": "post-slug",
/// "title": "Post Title",
/// "published_on": "2024-01-01 10:00:00",
/// "blogger": "Author Name",
/// "blog_intro": "Short introduction...",
/// "content": "<p>Full HTML content...</p>",
/// "meta_image": "https://...",
/// "meta_description": "SEO description",
/// "blog_category": "tin-tức"
/// },
/// ...
/// ]
/// }
/// ```
Future<List<BlogPostModel>> getBlogPosts() async {
try {
// Get Frappe session headers
final headers = await _frappeAuthService.getHeaders();
// Build full API URL
const url =
'${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeGetList}';
final response = await _dioClient.post<Map<String, dynamic>>(
url,
data: {
'doctype': 'Blog Post',
'fields': [
'name',
'title',
'published_on',
'blogger',
'blog_intro',
'content',
'meta_image',
'meta_description',
'blog_category',
],
'filters': {'published': 1},
'order_by': 'published_on desc',
'limit_page_length': 0,
},
options: Options(headers: headers),
);
if (response.data == null) {
throw Exception('Empty response from server');
}
// Parse the response using the wrapper model
final postsResponse = BlogPostsResponse.fromJson(response.data!);
return postsResponse.message;
} on DioException catch (e) {
if (e.response?.statusCode == 404) {
throw Exception('Blog posts endpoint not found');
} else if (e.response?.statusCode == 500) {
throw Exception('Server error while fetching blog posts');
} else if (e.type == DioExceptionType.connectionTimeout) {
throw Exception('Connection timeout while fetching blog posts');
} else if (e.type == DioExceptionType.receiveTimeout) {
throw Exception('Response timeout while fetching blog posts');
} else {
throw Exception('Failed to fetch blog posts: ${e.message}');
}
} catch (e) {
throw Exception('Unexpected error fetching blog posts: $e');
}
}
/// Get blog post detail by name
///
/// Fetches a single blog post by its unique name (slug) from Frappe.
/// Returns a [BlogPostModel].
///
/// API endpoint: POST https://land.dbiz.com/api/method/frappe.client.get
/// Request body:
/// ```json
/// {
/// "doctype": "Blog Post",
/// "name": "post-slug"
/// }
/// ```
///
/// Response format:
/// ```json
/// {
/// "message": {
/// "name": "post-slug",
/// "title": "Post Title",
/// "published_on": "2024-01-01 10:00:00",
/// "blogger": "Author Name",
/// "blog_intro": "Short introduction...",
/// "content": "<p>Full HTML content...</p>",
/// "meta_image": "https://...",
/// "meta_description": "SEO description",
/// "blog_category": "tin-tức"
/// }
/// }
/// ```
Future<BlogPostModel> getBlogPostDetail(String postName) async {
try {
// Get Frappe session headers
final headers = await _frappeAuthService.getHeaders();
// Build full API URL for frappe.client.get
const url =
'${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeGet}';
final response = await _dioClient.post<Map<String, dynamic>>(
url,
data: {
'doctype': 'Blog Post',
'name': postName,
},
options: Options(headers: headers),
);
if (response.data == null) {
throw Exception('Empty response from server');
}
// The response has the blog post data directly in "message" field
final messageData = response.data!['message'];
if (messageData == null) {
throw Exception('Blog post not found: $postName');
}
// Parse the blog post from the message field
return BlogPostModel.fromJson(messageData as Map<String, dynamic>);
} on DioException catch (e) {
if (e.response?.statusCode == 404) {
throw Exception('Blog post not found: $postName');
} else if (e.response?.statusCode == 500) {
throw Exception('Server error while fetching blog post detail');
} else if (e.type == DioExceptionType.connectionTimeout) {
throw Exception('Connection timeout while fetching blog post detail');
} else if (e.type == DioExceptionType.receiveTimeout) {
throw Exception('Response timeout while fetching blog post detail');
} else {
throw Exception('Failed to fetch blog post detail: ${e.message}');
}
} catch (e) {
throw Exception('Unexpected error fetching blog post detail: $e');
}
}
}