/// News Remote DataSource /// /// Handles fetching news/blog data from the Frappe API. library; import 'package:dio/dio.dart'; 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 /// /// Provides methods to fetch news and blog content from the Frappe API. /// Uses FrappeAuthService for session management. class NewsRemoteDataSource { NewsRemoteDataSource(this._dioClient, this._frappeAuthService); final DioClient _dioClient; final FrappeAuthService _frappeAuthService; /// Get blog categories /// /// Fetches all published blog categories from Frappe. /// Returns a list of [BlogCategoryModel]. /// /// API endpoint: POST https://land.dbiz.com/api/method/frappe.client.get_list /// Request body: /// ```json /// { /// "doctype": "Blog Category", /// "fields": ["title","name"], /// "filters": {"published":1}, /// "order_by": "creation desc", /// "limit_page_length": 0 /// } /// ``` /// /// Response format: /// ```json /// { /// "message": [ /// {"title": "Tin tức", "name": "tin-tức"}, /// {"title": "Chuyên môn", "name": "chuyên-môn"}, /// ... /// ] /// } /// ``` Future> getBlogCategories() async { try { // Get Frappe session headers final headers = await _frappeAuthService.getHeaders(); // Build full API URL final url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeGetList}'; final response = await _dioClient.post>( url, data: { 'doctype': 'Blog Category', 'fields': ['title', 'name'], 'filters': {'published': 1}, 'order_by': 'creation 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 categoriesResponse = BlogCategoriesResponse.fromJson(response.data!); return categoriesResponse.message; } on DioException catch (e) { if (e.response?.statusCode == 404) { throw Exception('Blog categories endpoint not found'); } else if (e.response?.statusCode == 500) { throw Exception('Server error while fetching blog categories'); } else if (e.type == DioExceptionType.connectionTimeout) { throw Exception('Connection timeout while fetching blog categories'); } else if (e.type == DioExceptionType.receiveTimeout) { throw Exception('Response timeout while fetching blog categories'); } else { throw Exception('Failed to fetch blog categories: ${e.message}'); } } catch (e) { 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": "

Full HTML content...

", /// "meta_image": "https://...", /// "meta_description": "SEO description", /// "blog_category": "tin-tức" /// }, /// ... /// ] /// } /// ``` Future> 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>( 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": "

Full HTML content...

", /// "meta_image": "https://...", /// "meta_description": "SEO description", /// "blog_category": "tin-tức" /// } /// } /// ``` Future 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>( 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); } 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'); } } }