510 lines
15 KiB
Dart
510 lines
15 KiB
Dart
import 'dart:convert';
|
|
import '../models/post_models.dart';
|
|
import '../models/comment_models.dart';
|
|
import 'http_service.dart';
|
|
import 'auth_service.dart';
|
|
import 'cache_service.dart';
|
|
|
|
class PostService {
|
|
static bool _pollingStarted = false;
|
|
static bool _userPostsPollingStarted = false;
|
|
|
|
/// Get all posts with caching - returns cached data if available and fresh
|
|
static Future<Map<String, dynamic>> getAllPosts({
|
|
bool forceRefresh = false,
|
|
}) async {
|
|
// Return cached data if available and not forcing refresh
|
|
if (!forceRefresh) {
|
|
final cached = CacheService.getCached<List<Post>>(
|
|
CacheService.postsAllKey,
|
|
);
|
|
if (cached != null &&
|
|
!CacheService.isCacheExpired(
|
|
CacheService.postsAllKey,
|
|
CacheService.postsCacheDuration,
|
|
)) {
|
|
return {
|
|
'success': true,
|
|
'message': 'Loaded from cache',
|
|
'posts': cached,
|
|
};
|
|
}
|
|
|
|
// Return stale data while fetching fresh data in background
|
|
final stale = CacheService.getCachedStale<List<Post>>(
|
|
CacheService.postsAllKey,
|
|
);
|
|
if (stale != null && !forceRefresh) {
|
|
// Trigger background refresh
|
|
_fetchAndCachePosts();
|
|
return {
|
|
'success': true,
|
|
'message': 'Loaded from cache (refreshing in background)',
|
|
'posts': stale,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Fetch fresh data
|
|
return await _fetchAndCachePosts();
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> _fetchAndCachePosts([bool fromPolling = false]) async {
|
|
try {
|
|
final response = await HttpService.get('/posts/all');
|
|
|
|
if (response.statusCode == 200) {
|
|
final responseData = jsonDecode(response.body);
|
|
final posts =
|
|
(responseData['data'] as List?)
|
|
?.map((post) => Post.fromJson(post))
|
|
.toList() ??
|
|
[];
|
|
|
|
// Only cache when not called from polling to prevent conflicts
|
|
if (!fromPolling) {
|
|
CacheService.setCached(CacheService.postsAllKey, posts);
|
|
}
|
|
|
|
return {
|
|
'success': responseData['status'] ?? false,
|
|
'message': responseData['message'] ?? '',
|
|
'posts': posts,
|
|
};
|
|
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
|
await AuthService.handleAuthenticationError();
|
|
return {
|
|
'success': false,
|
|
'message': 'Session expired. Please login again.',
|
|
'posts': <Post>[],
|
|
};
|
|
} else {
|
|
try {
|
|
final responseData = jsonDecode(response.body);
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to fetch posts',
|
|
'posts': <Post>[],
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
'success': false,
|
|
'message': 'Server error (${response.statusCode})',
|
|
'posts': <Post>[],
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching posts: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Network error: $e',
|
|
'posts': <Post>[],
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Start background polling for posts
|
|
static void startPolling() {
|
|
_pollingStarted = true;
|
|
CacheService.startPolling(
|
|
CacheService.postsAllKey,
|
|
() => _fetchAndCachePosts(true), // Mark as from polling
|
|
CacheService.postsPollingInterval,
|
|
CacheService.postsCacheDuration,
|
|
);
|
|
}
|
|
|
|
/// Stop background polling for posts
|
|
static void stopPolling() {
|
|
CacheService.stopPolling(CacheService.postsAllKey);
|
|
_pollingStarted = false;
|
|
}
|
|
|
|
/// Get posts stream for real-time updates
|
|
static Stream<List<Post>> getPostsStream() {
|
|
return CacheService.getStream<List<Post>>(CacheService.postsAllKey);
|
|
}
|
|
|
|
/// Force notify stream with current cached data (useful for initial stream setup)
|
|
static void notifyStreamWithCurrentData() {
|
|
final cached = CacheService.getCachedStale<List<Post>>(
|
|
CacheService.postsAllKey,
|
|
);
|
|
if (cached != null) {
|
|
// Re-notify the stream with current data
|
|
CacheService.notifyStreamListeners<List<Post>>(
|
|
CacheService.postsAllKey,
|
|
cached,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Start background polling for user posts
|
|
static void startUserPostsPolling() {
|
|
_userPostsPollingStarted = true;
|
|
CacheService.startPolling(
|
|
CacheService.userPostsKey,
|
|
() => _fetchAndCacheUserPosts(true), // Mark as from polling
|
|
CacheService.userPostsPollingInterval,
|
|
CacheService.userPostsCacheDuration,
|
|
);
|
|
}
|
|
|
|
/// Stop background polling for user posts
|
|
static void stopUserPostsPolling() {
|
|
CacheService.stopPolling(CacheService.userPostsKey);
|
|
_userPostsPollingStarted = false;
|
|
}
|
|
|
|
/// Get user posts stream for real-time updates
|
|
static Stream<List<Post>> getUserPostsStream() {
|
|
return CacheService.getStream<List<Post>>(CacheService.userPostsKey);
|
|
}
|
|
|
|
/// Force notify user posts stream with current cached data
|
|
static void notifyUserPostsStreamWithCurrentData() {
|
|
final cached = CacheService.getCachedStale<List<Post>>(
|
|
CacheService.userPostsKey,
|
|
);
|
|
if (cached != null) {
|
|
// Re-notify the stream with current data
|
|
CacheService.notifyStreamListeners<List<Post>>(
|
|
CacheService.userPostsKey,
|
|
cached,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Check if polling is active
|
|
static bool get isPollingActive => _pollingStarted;
|
|
|
|
/// Check if user posts polling is active
|
|
static bool get isUserPostsPollingActive => _userPostsPollingStarted;
|
|
|
|
/// Invalidate posts cache (useful after creating/updating posts)
|
|
static void invalidateCache() {
|
|
CacheService.clearCache(CacheService.postsAllKey);
|
|
}
|
|
|
|
/// Invalidate user posts cache (useful after creating posts)
|
|
static void invalidateUserPostsCache() {
|
|
CacheService.clearCache(CacheService.userPostsKey);
|
|
}
|
|
|
|
/// Get user posts with caching - returns cached data if available and fresh
|
|
static Future<Map<String, dynamic>> getUserPosts({
|
|
bool forceRefresh = false,
|
|
}) async {
|
|
// Return cached data if available and not forcing refresh
|
|
if (!forceRefresh) {
|
|
final cached = CacheService.getCached<List<Post>>(
|
|
CacheService.userPostsKey,
|
|
);
|
|
if (cached != null &&
|
|
!CacheService.isCacheExpired(
|
|
CacheService.userPostsKey,
|
|
CacheService.userPostsCacheDuration,
|
|
)) {
|
|
return {
|
|
'success': true,
|
|
'message': 'Loaded from cache',
|
|
'posts': cached,
|
|
};
|
|
}
|
|
|
|
// Return stale data while fetching fresh data in background
|
|
final stale = CacheService.getCachedStale<List<Post>>(
|
|
CacheService.userPostsKey,
|
|
);
|
|
if (stale != null && !forceRefresh) {
|
|
// Trigger background refresh
|
|
_fetchAndCacheUserPosts();
|
|
return {
|
|
'success': true,
|
|
'message': 'Loaded from cache (refreshing in background)',
|
|
'posts': stale,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Fetch fresh data
|
|
return await _fetchAndCacheUserPosts();
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> _fetchAndCacheUserPosts([bool fromPolling = false]) async {
|
|
try {
|
|
final response = await HttpService.get('/posts/user');
|
|
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
if (response.statusCode == 200) {
|
|
final posts = (responseData['data'] as List?)
|
|
?.map((post) => Post.fromJson(post))
|
|
.toList() ??
|
|
[];
|
|
|
|
// Only cache when not called from polling to prevent conflicts
|
|
if (!fromPolling) {
|
|
CacheService.setCached(CacheService.userPostsKey, posts);
|
|
}
|
|
|
|
return {
|
|
'success': responseData['status'] ?? false,
|
|
'message': responseData['message'] ?? '',
|
|
'posts': posts,
|
|
};
|
|
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
|
await AuthService.handleAuthenticationError();
|
|
return {
|
|
'success': false,
|
|
'message': 'Session expired. Please login again.',
|
|
'posts': <Post>[],
|
|
};
|
|
} else {
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to fetch user posts',
|
|
'posts': <Post>[],
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching user posts: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Network error: $e',
|
|
'posts': <Post>[],
|
|
};
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> createPost(String body) async {
|
|
try {
|
|
final createPostRequest = CreatePostRequest(body: body);
|
|
final response = await HttpService.post(
|
|
'/posts/create',
|
|
createPostRequest.toJson(),
|
|
);
|
|
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
// Invalidate both posts caches since we added a new post
|
|
invalidateCache();
|
|
invalidateUserPostsCache();
|
|
|
|
return {
|
|
'success': responseData['status'] ?? false,
|
|
'message': responseData['message'] ?? '',
|
|
'post': responseData['data'] != null
|
|
? Post.fromJson(responseData['data'])
|
|
: null,
|
|
};
|
|
} else {
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to create post',
|
|
'post': null,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Error creating post: $e');
|
|
return {'success': false, 'message': 'Network error: $e', 'post': null};
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> likePost(String postId) async {
|
|
try {
|
|
final response = await HttpService.post('/posts/like', {
|
|
'postId': postId,
|
|
});
|
|
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
if (response.statusCode == 200) {
|
|
return {
|
|
'success': responseData['status'] ?? false,
|
|
'message': responseData['message'] ?? '',
|
|
'post': responseData['data'] != null
|
|
? Post.fromJson(responseData['data'])
|
|
: null,
|
|
};
|
|
} else {
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to like post',
|
|
'post': null,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Error liking post: $e');
|
|
return {'success': false, 'message': 'Network error: $e', 'post': null};
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> unlikePost(String postId) async {
|
|
try {
|
|
final response = await HttpService.post('/posts/unlike', {
|
|
'postId': postId,
|
|
});
|
|
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
if (response.statusCode == 200) {
|
|
return {
|
|
'success': responseData['status'] ?? false,
|
|
'message': responseData['message'] ?? '',
|
|
'post': responseData['data'] != null
|
|
? Post.fromJson(responseData['data'])
|
|
: null,
|
|
};
|
|
} else {
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to unlike post',
|
|
'post': null,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Error unliking post: $e');
|
|
return {'success': false, 'message': 'Network error: $e', 'post': null};
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> getPost(String postId) async {
|
|
try {
|
|
final response = await HttpService.get('/posts/get?id=$postId');
|
|
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
if (response.statusCode == 200) {
|
|
return {
|
|
'success': responseData['status'] ?? false,
|
|
'message': responseData['message'] ?? '',
|
|
'post': responseData['data'] != null
|
|
? DetailedPost.fromJson(responseData['data'])
|
|
: null,
|
|
};
|
|
} else {
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to fetch post',
|
|
'post': null,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching post: $e');
|
|
return {'success': false, 'message': 'Network error: $e', 'post': null};
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> getComments(String postId) async {
|
|
try {
|
|
final response = await HttpService.get(
|
|
'/posts/comments/list?postId=$postId',
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final responseData = jsonDecode(response.body);
|
|
final commentsResponse = CommentsResponse.fromJson(responseData);
|
|
|
|
if (commentsResponse.status) {
|
|
return {'success': true, 'comments': commentsResponse.data};
|
|
} else {
|
|
return {
|
|
'success': false,
|
|
'message': commentsResponse.message ?? 'Failed to fetch comments',
|
|
'comments': <Comment>[],
|
|
};
|
|
}
|
|
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
|
await AuthService.handleAuthenticationError();
|
|
return {
|
|
'success': false,
|
|
'message': 'Session expired. Please login again.',
|
|
'comments': <Comment>[],
|
|
};
|
|
} else {
|
|
try {
|
|
final responseData = jsonDecode(response.body);
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to fetch comments',
|
|
'comments': <Comment>[],
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
'success': false,
|
|
'message': 'Server error (${response.statusCode})',
|
|
'comments': <Comment>[],
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching comments: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Network error: $e',
|
|
'comments': <Comment>[],
|
|
};
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> createComment({
|
|
required String postId,
|
|
required String body,
|
|
String? replyComment,
|
|
}) async {
|
|
try {
|
|
final requestBody = {'postId': int.parse(postId), 'body': body};
|
|
|
|
if (replyComment != null) {
|
|
requestBody['replyComment'] = int.parse(replyComment);
|
|
}
|
|
|
|
final response = await HttpService.post(
|
|
'/posts/comments/create',
|
|
requestBody,
|
|
);
|
|
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
if (responseData['status'] == true) {
|
|
return {
|
|
'success': true,
|
|
'comment': Comment.fromJson(responseData['data']),
|
|
};
|
|
} else {
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to create comment',
|
|
};
|
|
}
|
|
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
|
await AuthService.handleAuthenticationError();
|
|
return {
|
|
'success': false,
|
|
'message': 'Session expired. Please login again.',
|
|
};
|
|
} else {
|
|
try {
|
|
final responseData = jsonDecode(response.body);
|
|
return {
|
|
'success': false,
|
|
'message': responseData['message'] ?? 'Failed to create comment',
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
'success': false,
|
|
'message': 'Server error (${response.statusCode})',
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('Error creating comment: $e');
|
|
return {'success': false, 'message': 'Network error: $e'};
|
|
}
|
|
}
|
|
}
|