153 lines
4.5 KiB
Dart
153 lines
4.5 KiB
Dart
import 'dart:math';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
import '../../domain/models/feed_post_model.dart';
|
|
|
|
class PostService {
|
|
const PostService(this._client);
|
|
|
|
final SupabaseClient _client;
|
|
|
|
User get _currentUser {
|
|
final user = _client.auth.currentUser;
|
|
if (user == null) {
|
|
throw StateError('No authenticated user');
|
|
}
|
|
return user;
|
|
}
|
|
|
|
/// Fetches the global feed in chronological order.
|
|
Future<List<FeedPostModel>> fetchFeed() async {
|
|
final user = _currentUser;
|
|
|
|
// Fetch posts with user profile data using joins if possible,
|
|
// but sticking to the established pattern for consistency and safety.
|
|
final posts = await _client
|
|
.from('posts')
|
|
.select('id, user_id, caption, image_url, likes_count, created_at')
|
|
.order('created_at', ascending: false);
|
|
|
|
if (posts.isEmpty) return const [];
|
|
|
|
final postRows = List<Map<String, dynamic>>.from(posts);
|
|
final userIds = postRows
|
|
.map((e) => e['user_id'] as String?)
|
|
.whereType<String>()
|
|
.toSet()
|
|
.toList();
|
|
|
|
// Fetch profiles for the users in the feed
|
|
final profiles = userIds.isEmpty
|
|
? <Map<String, dynamic>>[]
|
|
: List<Map<String, dynamic>>.from(await _client
|
|
.from('profiles')
|
|
.select('user_id, username, avatar_url')
|
|
.inFilter('user_id', userIds));
|
|
|
|
final profileByUserId = {
|
|
for (final profile in profiles) (profile['user_id'] as String): profile,
|
|
};
|
|
|
|
// Fetch the current user's likes for these posts to set isLiked
|
|
final postIds = postRows.map((e) => e['id'] as String).toList();
|
|
final myLikes = postIds.isEmpty
|
|
? <Map<String, dynamic>>[]
|
|
: List<Map<String, dynamic>>.from(await _client
|
|
.from('post_likes')
|
|
.select('post_id')
|
|
.eq('user_id', user.id)
|
|
.inFilter('post_id', postIds));
|
|
|
|
final likedPostIds = myLikes.map((e) => e['post_id'] as String).toSet();
|
|
|
|
return postRows.map((row) {
|
|
final profile = profileByUserId[row['user_id']] ?? const {};
|
|
return FeedPostModel(
|
|
id: row['id'] as String,
|
|
userId: row['user_id'] as String,
|
|
caption: (row['caption'] as String?) ?? '',
|
|
imageUrl: (row['image_url'] as String?) ?? '',
|
|
createdAt: DateTime.parse(row['created_at'] as String),
|
|
likesCount: (row['likes_count'] as int?) ?? 0,
|
|
isLiked: likedPostIds.contains(row['id']),
|
|
username: (profile['username'] as String?) ?? 'riot_user',
|
|
avatarUrl: (profile['avatar_url'] as String?) ?? '',
|
|
);
|
|
}).toList();
|
|
}
|
|
|
|
/// Uploads a new post with an image and optional caption.
|
|
Future<void> uploadPost({
|
|
required String caption,
|
|
required Uint8List imageBytes,
|
|
required String extension,
|
|
}) async {
|
|
final user = _currentUser;
|
|
final ts = DateTime.now().millisecondsSinceEpoch;
|
|
final random = Random().nextInt(99999);
|
|
final fileName = '$ts-$random.$extension';
|
|
final path = 'posts/${user.id}/$fileName';
|
|
|
|
// Upload image to Supabase Storage
|
|
await _client.storage.from('post-images').uploadBinary(
|
|
path,
|
|
imageBytes,
|
|
fileOptions: const FileOptions(upsert: false),
|
|
);
|
|
|
|
final imageUrl = _client.storage.from('post-images').getPublicUrl(path);
|
|
|
|
// Insert post record into PostgreSQL
|
|
await _client.from('posts').insert({
|
|
'user_id': user.id,
|
|
'caption': caption.trim(),
|
|
'image_url': imageUrl,
|
|
'likes_count': 0,
|
|
});
|
|
}
|
|
|
|
/// Toggles a like on a post.
|
|
Future<void> toggleLike({
|
|
required String postId,
|
|
required bool currentlyLiked,
|
|
required int currentLikesCount,
|
|
}) async {
|
|
final user = _currentUser;
|
|
|
|
if (currentlyLiked) {
|
|
// Unlike
|
|
await _client
|
|
.from('post_likes')
|
|
.delete()
|
|
.eq('post_id', postId)
|
|
.eq('user_id', user.id);
|
|
|
|
await _client.from('posts').update({
|
|
'likes_count': max(0, currentLikesCount - 1),
|
|
}).eq('id', postId);
|
|
} else {
|
|
// Like
|
|
await _client.from('post_likes').upsert({
|
|
'post_id': postId,
|
|
'user_id': user.id,
|
|
});
|
|
|
|
await _client.from('posts').update({
|
|
'likes_count': currentLikesCount + 1,
|
|
}).eq('id', postId);
|
|
}
|
|
}
|
|
|
|
/// Deletes a post belonging to the current user.
|
|
Future<void> deletePost(String postId) async {
|
|
final user = _currentUser;
|
|
await _client
|
|
.from('posts')
|
|
.delete()
|
|
.eq('id', postId)
|
|
.eq('user_id', user.id);
|
|
}
|
|
}
|