Skip to content

Commit

Permalink
feat: new post with multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
MikkyBoy357 committed Aug 20, 2024
1 parent 13d128d commit b999484
Show file tree
Hide file tree
Showing 15 changed files with 420 additions and 4 deletions.
8 changes: 8 additions & 0 deletions .idea/libraries/Dart_Packages.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lib/common/constants/storage_directories.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ class StorageDirectories {
static String avatarById({required String userId}) {
return 'avatars/$userId';
}

static String postsByUserId({required String userId}) {
return 'posts/$userId';
}
}
1 change: 1 addition & 0 deletions lib/common/extensions/db_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ extension DbX on Db {
DbCollection get transactionsCollection => collection('transactions');
DbCollection get chatsCollection => collection('chats');
DbCollection get chatMessagesCollection => collection('chatMessages');
DbCollection get postsCollection => collection('posts');
}
1 change: 1 addition & 0 deletions lib/common/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export 'cat.dart';
export 'followed_follower.dart';
export 'followers_followings_counts.dart';
export 'messaging/messaging.dart';
export 'post.dart';
export 'transaction.dart';
export 'user.dart';
export 'wallet.dart';
Expand Down
57 changes: 57 additions & 0 deletions lib/common/models/post.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:mongo_dart/mongo_dart.dart';

class Post {
final ObjectId id;
final ObjectId userId;
final String caption;
final List<String> mediaUrls;
final DateTime postTimestamp;

Post({
required this.id,
required this.userId,
required this.caption,
this.mediaUrls = const [],
required this.postTimestamp,
});

factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['_id'] as ObjectId,
userId: json['userId'] as ObjectId,
caption: json['caption'] as String,
mediaUrls: (json['mediaUrls'] == null)
? <String>[]
: (json['mediaUrls'] as List).map((e) => e as String).toList(),
postTimestamp: json['postTimestamp'] != null
? DateTime.parse(json['postTimestamp'].toString())
: DateTime.now(),
);
}

Map<String, dynamic> toJson() {
return {
'_id': id,
'userId': userId,
'caption': caption,
'mediaUrls': mediaUrls,
'postTimestamp': postTimestamp.toString(),
};
}

Post copyWith({
ObjectId? id,
ObjectId? userId,
String? caption,
List<String>? mediaUrls,
DateTime? postTimestamp,
}) {
return Post(
id: id ?? this.id,
userId: userId ?? this.userId,
caption: caption ?? this.caption,
mediaUrls: mediaUrls ?? this.mediaUrls,
postTimestamp: postTimestamp ?? this.postTimestamp,
);
}
}
48 changes: 45 additions & 3 deletions lib/data/repositories/file_upload/file_upload.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:typed_data';

import 'package:cats_backend/common/common.dart';
import 'package:cats_backend/services/services.dart';
import 'package:dart_frog/dart_frog.dart';

Expand All @@ -20,6 +21,45 @@ class FileUpload {
return firstFile;
}

static Future<List<UploadedFile>> getFilesFromFormData(
FormData formData,
) async {
printYellow('Files found in the form data: ${formData.files}');
final keys = formData.files.keys.toList();
printYellow('Keys: $keys');
printYellow('Keys: ${formData.files['media']}');
final files = keys.map((key) => formData.files[key]!).toList();
return files;
}

static Future<List<UrlOrError>> uploadMultipleFilesAndReturnUrls({
required List<UploadedFile> uploadedFiles,
required String storageDir,
double maxSizeInMB = 1,
int maxFiles = 2,
}) async {
final urls = <UrlOrError>[];

if (uploadedFiles.length > maxFiles) {
return [
for (var i = 0; i < maxFiles; i++)
(url: null, error: 'Max files exceeded\nMax files: $maxFiles'),
];
}

printGreen('Uploading ${uploadedFiles.length} files...');
for (final uploadedFile in uploadedFiles) {
final urlOrError = await uploadFileAndReturnUrl(
uploadedFile: uploadedFile,
storageDir: storageDir,
maxSizeInMB: maxSizeInMB,
);
urls.add(urlOrError);
}

return urls;
}

static Future<UrlOrError> uploadFileAndReturnUrl({
required UploadedFile uploadedFile,
required String storageDir,
Expand All @@ -32,7 +72,9 @@ class FileUpload {
final maxBytes = maxSizeInMB * 1024 * 1024;
final fileSize = data.lengthInBytes;
if (fileSize >= maxBytes) {
print('====> ⚠️ File ($fileSize B) exceeds max size ($maxBytes B) <====');
printRed(
'====> ⚠️ File ($fileSize B) exceeds max size ($maxBytes B) <====',
);
return (
url: null,
error: 'File exceeds max size\n'
Expand All @@ -53,14 +95,14 @@ class FileUpload {
// Wait for upload to complete
// and return the download URL
final snapshot = await uploadTask.whenComplete(() {
print('====> Upload complete (${ref.fullPath})');
printGreen('====> Upload complete (${ref.fullPath})');
});

try {
final downloadUrl = await snapshot.ref.getDownloadURL();
return (url: downloadUrl, error: null);
} catch (e) {
print('====> Error getting download URL: $e <====');
printRed('====> Error getting download URL: $e <====');
}

return (url: null, error: 'Error getting download URL');
Expand Down
71 changes: 71 additions & 0 deletions lib/data/repositories/post/post_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'package:cats_backend/common/common.dart';
import 'package:mongo_dart/mongo_dart.dart';

abstract class PostRepositoryImpl {
Future<Post?> createPost({
required ObjectId userId,
required String caption,
List<String>? mediaUrls,
});

Future<Post?> getPostById({required ObjectId postId});

Future<List<Post?>> getPostsByUserId({required ObjectId userId});

// Future<Post?> getPostById({required ObjectId postId});

// Future<Post?> updatePost({
// required ObjectId postId,
// required String caption,
// List<String>? mediaUrls,
// });

// Future<bool?> deletePost({required ObjectId postId});
}

class PostRepository extends PostRepositoryImpl {
final Db _database;

PostRepository({
required Db database,
}) : _database = database;

DbCollection get _postsCollection => _database.collection('posts');

@override
Future<Post?> createPost({
required ObjectId userId,
required String caption,
List<String>? mediaUrls,
}) async {
final post = Post(
id: ObjectId(),
userId: userId,
caption: caption,
mediaUrls: mediaUrls ?? [],
postTimestamp: DateTime.now(),
);

await _postsCollection.insert(post.toJson());

return post;
}

@override
Future<Post?> getPostById({required ObjectId postId}) async {
final post = await _postsCollection.findOne({
'_id': postId,
});

return post == null ? null : Post.fromJson(post);
}

@override
Future<List<Post?>> getPostsByUserId({required ObjectId userId}) async {
final posts = await _postsCollection.find({
'userId': userId,
}).toList();

return posts.map((e) => Post.fromJson(e)).toList();
}
}
1 change: 1 addition & 0 deletions lib/data/repositories/repositories.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export 'auth/auth.dart';
export 'buss/buss_repository.dart';
export 'cat/cat_repository.dart';
export 'chat/chat_repository.dart';
export 'post/post_repository.dart';
export 'profile/profile_repository.dart';
114 changes: 114 additions & 0 deletions lib/data/request_handlers/post.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import 'package:cats_backend/common/common.dart';
import 'package:dart_frog/dart_frog.dart';
import 'package:mongo_dart/mongo_dart.dart';

import '../../common/constants/storage_directories.dart';
import '../repositories/file_upload/file_upload.dart';
import '../repositories/post/post_repository.dart';

abstract class PostRequestHandler {
Future<Response> handleCreatePost({
required User saint,
required FormData formData,
});
Future<Response> handleGetPostById({
required ObjectId postId,
});
Future<Response> handleGetPostsByUserId({
required ObjectId userId,
});
}

class PostRequestHandlerImpl implements PostRequestHandler {
final PostRepository _postRepository;

const PostRequestHandlerImpl({
required PostRepository postRepository,
}) : _postRepository = postRepository;

@override
Future<Response> handleCreatePost({
required User saint,
required FormData formData,
}) async {
print('===> POST <==> Post:');
final errors = <String?>[];
final mediaUrls = <String>[];

final caption = formData.fields['caption'];
if (caption == null) {
return Response.json(
body: 'Error: Caption is required.',
statusCode: 400,
);
}

/// Check if there is image in the form data
final files = formData.files;
if (files.isNotEmpty) {
final files = await FileUpload.getFilesFromFormData(formData);
printYellow('MyFiles: \n${files.map((e) => '${e.name}\n')}');
printGreen('${files.length} files found in the form data.');

final uploadResults = await FileUpload.uploadMultipleFilesAndReturnUrls(
uploadedFiles: files,
storageDir: StorageDirectories.postsByUserId(userId: saint.$_id.oid),
maxFiles: 3,
);

errors.addAll(
uploadResults.map((e) => e.error).toList().where((e) => e != null),
);
mediaUrls.addAll(
uploadResults.where((e) => e.url != null).map((e) => e.url!).toList(),
);
} else {
printYellow('No files found in the form data.');
}

if (errors.isNotEmpty) {
return Response.json(
body: {
'message': 'Failed to upload image',
'errors': errors,
},
statusCode: 500,
);
}

final post = await _postRepository.createPost(
userId: saint.$_id,
caption: caption,
mediaUrls: mediaUrls,
);

return Response.json(
body: post,
statusCode: post != null ? 201 : 400,
);
}

@override
Future<Response> handleGetPostById({
required ObjectId postId,
}) async {
final post = await _postRepository.getPostById(postId: postId);

return Response.json(
body: post,
statusCode: post != null ? 200 : 404,
);
}

@override
Future<Response> handleGetPostsByUserId({
required ObjectId userId,
}) async {
final posts = await _postRepository.getPostsByUserId(userId: userId);
return Response.json(
body: posts,
statusCode: posts.isNotEmpty ? 200 : 404,
);
}
}
1 change: 1 addition & 0 deletions lib/data/request_handlers/request_handlers.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export 'buss.dart';
export 'cats.dart';
export 'chat.dart';
export 'post.dart';
export 'profile.dart';
export 'user.dart';
4 changes: 3 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: cats_backend
description: An new Dart Frog application
description: An action Dart Frog application
version: 1.0.0+1
publish_to: none

Expand All @@ -14,6 +14,8 @@ dependencies:
firebase_dart: ^1.1.4
jaguar_jwt: ^3.0.0
mongo_dart: ^0.10.3
shelf_multipart: ^1.0.0
shelf:

dev_dependencies:
mocktail: ^1.0.0
Expand Down
Loading

0 comments on commit b999484

Please sign in to comment.