From 3e8b5c8bf78b3198b4da302864962d275fef5186 Mon Sep 17 00:00:00 2001 From: YJU Date: Thu, 4 Apr 2024 01:51:32 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[Init]=20#520=20-=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AA=A8=EB=8D=B8=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HappyAnding/HappyAnding/Model/Answer.swift | 33 ++++++++ .../HappyAnding/Model/CommunityComment.swift | 34 +++++++++ HappyAnding/HappyAnding/Model/Post.swift | 42 +++++++++++ .../Repository/CommunityRepository.swift | 75 +++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 HappyAnding/HappyAnding/Model/Answer.swift create mode 100644 HappyAnding/HappyAnding/Model/CommunityComment.swift create mode 100644 HappyAnding/HappyAnding/Model/Post.swift create mode 100644 HappyAnding/HappyAnding/Repository/CommunityRepository.swift diff --git a/HappyAnding/HappyAnding/Model/Answer.swift b/HappyAnding/HappyAnding/Model/Answer.swift new file mode 100644 index 00000000..88b1f4a4 --- /dev/null +++ b/HappyAnding/HappyAnding/Model/Answer.swift @@ -0,0 +1,33 @@ +// +// Answer.swift +// HappyAnding +// +// Created by 임정욱 on 4/4/24. +// + +import Foundation + +struct Answer: Identifiable, Codable, Equatable, Hashable { + var id = UUID().uuidString + var body: String = "" + var postedBy: String = "" + var postedAt:[String] = [Date().getDate()] + var images: [String] = [] + var likesCount: Int = 0 + var isAccepted: Bool = false + var comments: [String] = [] + + var dictionary: [String: Any] { + let data = (try? JSONEncoder().encode(self)) ?? Data() + return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] + } + + init(body: String, postedBy: String, images: [String] = [], likesCount: Int = 0, isAccepted: Bool = false, comments: [String] = []) { + self.body = body + self.postedBy = postedBy + self.images = images + self.likesCount = likesCount + self.isAccepted = isAccepted + self.comments = comments + } +} diff --git a/HappyAnding/HappyAnding/Model/CommunityComment.swift b/HappyAnding/HappyAnding/Model/CommunityComment.swift new file mode 100644 index 00000000..82e993a3 --- /dev/null +++ b/HappyAnding/HappyAnding/Model/CommunityComment.swift @@ -0,0 +1,34 @@ +// +// CommunityComment.swift +// HappyAnding +// +// Created by 임정욱 on 4/4/24. +// + +import Foundation + + +struct CommunityComment: Identifiable, Codable, Equatable, Hashable { + var id: String = UUID().uuidString + var body: String + var author: String + var postedAt: String = Date().getDate() + var likesCount: Int = 0 + var isAccepted: Bool = false + var comments: [String] = [] + + + var dictionary: [String: Any] { + let data = (try? JSONEncoder().encode(self)) ?? Data() + return (try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]) ?? [:] + } + + + init(body: String, author: String, images: [String] = [], likesCount: Int = 0, isAccepted: Bool = false, comments: [String] = []) { + self.body = body + self.author = author + self.likesCount = likesCount + self.isAccepted = isAccepted + self.comments = comments + } +} diff --git a/HappyAnding/HappyAnding/Model/Post.swift b/HappyAnding/HappyAnding/Model/Post.swift new file mode 100644 index 00000000..b53a4e34 --- /dev/null +++ b/HappyAnding/HappyAnding/Model/Post.swift @@ -0,0 +1,42 @@ +// +// Post.swift +// HappyAnding +// +// Created by 임정욱 on 4/4/24. +// + +import Foundation + +struct Post: Identifiable, Codable, Equatable, Hashable { + var id = UUID().uuidString + var type: String = "" + var title: String = "" + var body: String = "" + var postedBy: String + var postedAt:[String] = [Date().getDate()] + var images: [String] = [] + var likesCount: Int = 0 + var commentsCount: Int + var tags: [String] = [] + var comments: [String] = [] + var answers: [String] = [] + + + var dictionary: [String: Any] { + let data = (try? JSONEncoder().encode(self)) ?? Data() + return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] + } + + init(type: String, title: String, body: String, postedBy: String, images: [String], likesCount: Int, commentsCount: Int, tags: [String], comments: [String], answers: [String]) { + self.type = type + self.title = title + self.body = body + self.postedBy = postedBy + self.images = images + self.likesCount = likesCount + self.commentsCount = commentsCount + self.tags = tags + self.comments = comments + self.answers = answers + } +} diff --git a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift new file mode 100644 index 00000000..76782463 --- /dev/null +++ b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift @@ -0,0 +1,75 @@ +// +// File.swift +// HappyAnding +// +// Created by 임정욱 on 4/3/24. +// + +import Foundation +import FirebaseCore +import FirebaseFirestore +import FirebaseFirestoreSwift +import FirebaseAuth + + + +class CommunityRepository { + + private let db = Firestore.firestore() + + + + +//MARK: - 글 관련 함수 + + func getPosts() { + + } + + func createPost() { + + } + + func updatePost() { + + } + + func deletePost() { + + } + + func likePost() { + + } + + func unLikePost() { + + } + + +//MARK: - 댓글 관련 함수 + + func getComments() { + + } + + func createComment() { + + } + + func updateComment() { + + } + + func deleteComment() { + + } + + func likeComment() { + + } + + func unLikeComment() { + + } +} From 468c5cb93201624fe4636d79526e194721e9acbf Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 12 Apr 2024 10:18:55 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[Feat]=20#520=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HappyAnding/HappyAnding/Model/Answer.swift | 43 +++++++++------ .../HappyAnding/Model/CommunityComment.swift | 42 ++++++++------ HappyAnding/HappyAnding/Model/Post.swift | 55 ++++++++++--------- HappyAnding/HappyAnding/Model/PostType.swift | 13 +++++ 4 files changed, 94 insertions(+), 59 deletions(-) create mode 100644 HappyAnding/HappyAnding/Model/PostType.swift diff --git a/HappyAnding/HappyAnding/Model/Answer.swift b/HappyAnding/HappyAnding/Model/Answer.swift index 88b1f4a4..41d3fd8c 100644 --- a/HappyAnding/HappyAnding/Model/Answer.swift +++ b/HappyAnding/HappyAnding/Model/Answer.swift @@ -8,26 +8,35 @@ import Foundation struct Answer: Identifiable, Codable, Equatable, Hashable { - var id = UUID().uuidString - var body: String = "" - var postedBy: String = "" - var postedAt:[String] = [Date().getDate()] - var images: [String] = [] - var likesCount: Int = 0 - var isAccepted: Bool = false - var comments: [String] = [] + + let id: String + let postId : String + let createdAt: String + let author: String + + var content: String + var isAccepted: Bool + var images: [String] + var likedBy: [String:Bool] + var likeCount: Int + + init(content: String, author: String, postId:String, images: [String] = []) { + + self.id = UUID().uuidString + self.createdAt = Date().getDate() + + self.content = content + self.isAccepted = false + self.author = author + self.postId = postId + self.images = images + + self.likeCount = 0 + self.likedBy = [:] + } var dictionary: [String: Any] { let data = (try? JSONEncoder().encode(self)) ?? Data() return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] } - - init(body: String, postedBy: String, images: [String] = [], likesCount: Int = 0, isAccepted: Bool = false, comments: [String] = []) { - self.body = body - self.postedBy = postedBy - self.images = images - self.likesCount = likesCount - self.isAccepted = isAccepted - self.comments = comments - } } diff --git a/HappyAnding/HappyAnding/Model/CommunityComment.swift b/HappyAnding/HappyAnding/Model/CommunityComment.swift index 82e993a3..9ce515c0 100644 --- a/HappyAnding/HappyAnding/Model/CommunityComment.swift +++ b/HappyAnding/HappyAnding/Model/CommunityComment.swift @@ -9,26 +9,36 @@ import Foundation struct CommunityComment: Identifiable, Codable, Equatable, Hashable { - var id: String = UUID().uuidString - var body: String - var author: String - var postedAt: String = Date().getDate() - var likesCount: Int = 0 - var isAccepted: Bool = false - var comments: [String] = [] - + + let id: String + let createdAt: String + let postId: String + let author: String + let parent: String? + + var content: String + var likeCount: Int + var likedBy: [String:Bool] + var isAccepted: Bool + init(content: String, author: String,postId : String, parent: String? = nil) { + + self.id = UUID().uuidString + self.createdAt = Date().getDate() + + self.content = content + self.author = author + self.parent = parent + self.postId = postId + + self.likeCount = 0 + self.likedBy = [:] + self.isAccepted = false + } + var dictionary: [String: Any] { let data = (try? JSONEncoder().encode(self)) ?? Data() return (try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]) ?? [:] } - - init(body: String, author: String, images: [String] = [], likesCount: Int = 0, isAccepted: Bool = false, comments: [String] = []) { - self.body = body - self.author = author - self.likesCount = likesCount - self.isAccepted = isAccepted - self.comments = comments - } } diff --git a/HappyAnding/HappyAnding/Model/Post.swift b/HappyAnding/HappyAnding/Model/Post.swift index b53a4e34..5076be8f 100644 --- a/HappyAnding/HappyAnding/Model/Post.swift +++ b/HappyAnding/HappyAnding/Model/Post.swift @@ -8,35 +8,38 @@ import Foundation struct Post: Identifiable, Codable, Equatable, Hashable { - var id = UUID().uuidString - var type: String = "" - var title: String = "" - var body: String = "" - var postedBy: String - var postedAt:[String] = [Date().getDate()] - var images: [String] = [] - var likesCount: Int = 0 - var commentsCount: Int - var tags: [String] = [] - var comments: [String] = [] - var answers: [String] = [] - - var dictionary: [String: Any] { - let data = (try? JSONEncoder().encode(self)) ?? Data() - return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] - } + let id : String + let type: PostType + let createdAt: String + let author: String + + var content: String + var shortcuts: [String] + var images: [String] + var likedBy: [String:Bool] + var likeCount: Int + var commentCount: Int - init(type: String, title: String, body: String, postedBy: String, images: [String], likesCount: Int, commentsCount: Int, tags: [String], comments: [String], answers: [String]) { + init(type: PostType, content: String, author: String, shortcuts: [String] = [], images: [String] = []) { + + self.id = UUID().uuidString + self.createdAt = Date().getDate() + self.type = type - self.title = title - self.body = body - self.postedBy = postedBy + self.content = content + self.author = author + self.shortcuts = shortcuts self.images = images - self.likesCount = likesCount - self.commentsCount = commentsCount - self.tags = tags - self.comments = comments - self.answers = answers + + self.likeCount = 0 + self.commentCount = 0 + self.likedBy = [:] + + } + + var dictionary: [String: Any] { + let data = (try? JSONEncoder().encode(self)) ?? Data() + return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] } } diff --git a/HappyAnding/HappyAnding/Model/PostType.swift b/HappyAnding/HappyAnding/Model/PostType.swift new file mode 100644 index 00000000..64796426 --- /dev/null +++ b/HappyAnding/HappyAnding/Model/PostType.swift @@ -0,0 +1,13 @@ +// +// PostType.swift +// HappyAnding +// +// Created by 임정욱 on 4/4/24. +// + +import Foundation + +enum PostType: String, Codable { + case General = "General" + case Question = "Question" +} From cd2bfbe39ea53a551242564015e77c05ce425995 Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 12 Apr 2024 10:19:22 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[Feat]=20#520=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=A0=9C=EC=99=B8=20CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/CommunityRepository.swift | 430 +++++++++++++++++- 1 file changed, 413 insertions(+), 17 deletions(-) diff --git a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift index 76782463..e60b4104 100644 --- a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift +++ b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift @@ -17,59 +17,455 @@ class CommunityRepository { private let db = Firestore.firestore() + private let postCollection: String = "Post" + private let communityCommentCollection: String = "CommunityComment" + private let answerCollection: String = "Answer" + //MARK: - 글 관련 함수 - func getPosts() { - + + // 모든 글 가져오기 + func getPosts(completion: @escaping ([Post]) -> Void) { + db.collection(postCollection) + .order(by: "createdAt", descending: true) // 최신 글부터 정렬 + .getDocuments { snapshot, error in + if error != nil { + completion([]) + return + } + let posts = snapshot?.documents.compactMap { document in + try? document.data(as: Post.self) + } ?? [] + completion(posts) + } + } + + // 글 가져오기 무한스크롤 + func getPosts(limit: Int, lastCreatedAt: String? = nil, completion: @escaping ([Post]) -> Void) { + var query: Query = db.collection(postCollection) + .order(by: "createdAt", descending: true) + .limit(to: limit) + + if let lastCreatedAt = lastCreatedAt { + query = query.start(after: [lastCreatedAt]) + } + + query.getDocuments { snapshot, error in + guard let snapshot = snapshot, error == nil else { + completion([]) + return + } + + let posts = snapshot.documents.compactMap { document -> Post? in + try? document.data(as: Post.self) + } + + completion(posts) + } } - func createPost() { - + + // 글 생성 + func createPost(post: Post, completion: @escaping (Bool) -> Void) { + let documentId = post.id + do { + try db.collection(postCollection).document(documentId).setData(from: post) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } catch { + completion(false) + } } + + // 글 업데이트 + func updatePost(postid: String, content: String? = nil, shortcuts: [String]? = nil, images: [String]? = nil, completion: @escaping (Bool) -> Void) { - func updatePost() { + var updateFields: [String: Any] = [:] + if let content = content { + updateFields["content"] = content + } + if let shortcuts = shortcuts { + updateFields["shortcuts"] = shortcuts + } + if let images = images { + updateFields["images"] = images + } + + if !updateFields.isEmpty { + db.collection(postCollection).document(postid).updateData(updateFields) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } } - - func deletePost() { + + // 글 삭제 + func deletePost(postId: String, completion: @escaping (Bool) -> Void) { + let group = DispatchGroup() + var overallSuccess = true + + // 게시물 삭제 + group.enter() + db.collection(postCollection).document(postId).delete { error in + if error != nil { + overallSuccess = false + } + group.leave() + } + + // 관련된 답변(Answer) 삭제 + group.enter() + db.collection(answerCollection).whereField("postId", isEqualTo: postId).getDocuments { (snapshot, error) in + guard let documents = snapshot?.documents else { + overallSuccess = false + group.leave() + return + } + for document in documents { + document.reference.delete() + } + group.leave() + } + // 관련된 댓글(CommunityComment) 삭제 + group.enter() + db.collection(communityCommentCollection).whereField("postId", isEqualTo: postId).getDocuments { (snapshot, error) in + guard let documents = snapshot?.documents else { + overallSuccess = false + group.leave() + return + } + for document in documents { + document.reference.delete() + } + group.leave() + } + + // 모든 삭제 작업이 완료되었는지 확인 + group.notify(queue: .main) { + completion(overallSuccess) + } } - func likePost() { + + // 글에 좋아요 추가 + func likePost(postId: String, userId: String, completion: @escaping (Bool) -> Void) { + let postRef = db.collection(postCollection).document(postId) + + postRef.getDocument { (document, error) in + if let document = document, let data = document.data(), error == nil { + var likedBy = data["likedBy"] as? [String: Bool] ?? [:] + + if likedBy[userId] != true { + likedBy[userId] = true + let likeCount = (data["likeCount"] as? Int ?? 0) + 1 + + postRef.updateData([ + "likedBy": likedBy, + "likeCount": likeCount + ]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } + } else { + completion(false) + } + } + } + + // 글에 좋아요 제거 + func unlikePost(postId: String, userId: String, completion: @escaping (Bool) -> Void) { + let postRef = db.collection(postCollection).document(postId) + postRef.getDocument { (document, error) in + if let document = document, let data = document.data(), error == nil { + var likedBy = data["likedBy"] as? [String: Bool] ?? [:] + + if likedBy[userId] == true { + likedBy.removeValue(forKey: userId) + let likeCount = max((data["likeCount"] as? Int ?? 0) - 1, 0) + + postRef.updateData([ + "likedBy": likedBy, + "likeCount": likeCount + ]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } + } else { + completion(false) + } + } } - func unLikePost() { + +//MARK: - 답변 관련 함수 + + func getAnswers(for postId: String, completion: @escaping ([Answer]) -> Void) { + db.collection(answerCollection) + .whereField("postId", isEqualTo: postId) + .order(by: "createdAt", descending: true) + .getDocuments { snapshot, error in + + if error != nil { + completion([]) + return + } + + let answers = snapshot?.documents.compactMap { document -> Answer? in + try? document.data(as: Answer.self) + } ?? [] + completion(answers) + } + } + + func createAnswer(answer: Answer, completion: @escaping (Bool) -> Void) { + let documentId = answer.id + do { + try db.collection(answerCollection).document(documentId).setData(from: answer) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } catch { + completion(false) + } + } + + func updateAnswer(answerId: String, content: String? = nil, images: [String]? = nil, completion: @escaping (Bool) -> Void) { + + var updateFields: [String: Any] = [:] + if let newContent = content { + updateFields["content"] = newContent + } + + if let newImages = images { + updateFields["images"] = newImages + } + + if !updateFields.isEmpty { + db.collection(answerCollection).document(answerId).updateData(updateFields) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } + } + + func acceptAnswer(answerId: String, completion: @escaping (Bool) -> Void) { + let answerRef = db.collection(answerCollection).document(answerId) + + answerRef.updateData(["isAccepted": true]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } } + func deleteAnswer(answerId: String, completion: @escaping (Bool) -> Void) { + db.collection(answerCollection).document(answerId) + .delete() { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } + //MARK: - 댓글 관련 함수 - func getComments() { - + // 특정 게시물의 댓글 가져오기 + func getComments(postId: String, completion: @escaping ([CommunityComment]) -> Void) { + db.collection(communityCommentCollection) + .whereField("postId", isEqualTo: postId) + .order(by: "createdAt", descending: true) + .getDocuments { (snapshot, error) in + guard let documents = snapshot?.documents, error == nil else { + completion([]) + return + } + let comments = documents.compactMap { document -> CommunityComment? in + try? document.data(as: CommunityComment.self) + } + completion(comments) + } } - func createComment() { - + // 댓글 생성 + func createComment(comment: CommunityComment, completion: @escaping (Bool) -> Void) { + let documentId = comment.id + let postId = comment.postId + + + do { + try db.collection(communityCommentCollection).document(documentId).setData(from: comment) { error in + if error != nil { + completion(false) + } else { + let postRef = self.db.collection(self.postCollection).document(postId) + postRef.updateData(["commentCount": FieldValue.increment(1.0)]) { error in + if error != nil { + completion(false) + } else { + + completion(true) + } + } + } + } + } catch { + completion(false) + } } - func updateComment() { + // 댓글 수정 + func updateComment(commentId: String, content: String?, completion: @escaping (Bool) -> Void) { + var updates: [String: Any] = [:] + if let newContent = content { + updates["content"] = newContent + } + + if !updates.isEmpty { + db.collection(communityCommentCollection).document(commentId).updateData(updates) { error in + if let error = error { + print("안녕") + print(error) + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } } - func deleteComment() { + // 댓글 삭제 + func deleteComment(commentId: String, completion: @escaping (Bool) -> Void) { + let commentRef = db.collection(communityCommentCollection).document(commentId) + commentRef.getDocument { (document, error) in + guard let document = document, let commentData = document.data(), let postId = commentData["postId"] as? String else { + completion(false) + return + } + + commentRef.delete() { error in + if error != nil { + completion(false) + } else { + let postRef = self.db.collection(self.postCollection).document(postId) + postRef.updateData(["commentCount": FieldValue.increment(-1.0)]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } + } + } } - func likeComment() { + // 댓글에 좋아요 추가 + func likeComment(commentId: String, userId: String, completion: @escaping (Bool) -> Void) { + let commentRef = db.collection(communityCommentCollection).document(commentId) + commentRef.getDocument { (document, error) in + if let document = document, let data = document.data(), error == nil { + var likedBy = data["likedBy"] as? [String: Bool] ?? [:] + + if likedBy[userId] != true { + likedBy[userId] = true + + let likeCount = (data["likeCount"] as? Int ?? 0) + 1 + + commentRef.updateData([ + "likedBy": likedBy, + "likeCount": likeCount + ]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } + } else { + completion(false) + } + } } - func unLikeComment() { + // 댓글에 좋아요 제거 + func unlikeComment(commentId: String, userId: String, completion: @escaping (Bool) -> Void) { + let commentRef = db.collection(communityCommentCollection).document(commentId) + commentRef.getDocument { documentSnapshot, error in + guard let document = documentSnapshot, let data = document.data(), error == nil else { + completion(false) + return + } + + var likedBy = data["likedBy"] as? [String: Bool] ?? [:] + + if likedBy[userId] == true { + likedBy.removeValue(forKey: userId) + + let likeCount = max((data["likeCount"] as? Int ?? 0) - 1, 0) + + commentRef.updateData([ + "likedBy": likedBy, + "likeCount": likeCount + ]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } + } } } From 57c3a72d09911adb96d698579bff679f1011a7e9 Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 12 Apr 2024 10:19:35 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[Test]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8=20CRUD=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommunityRepositoryTest.swift | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift diff --git a/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift b/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift new file mode 100644 index 00000000..647b6308 --- /dev/null +++ b/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift @@ -0,0 +1,323 @@ +// +// CommunityRepositoryTest.swift +// HappyAndingTests +// +// Created by 임정욱 on 4/4/24. +// + +import XCTest +@testable import HappyAnding // YourAppName을 앱의 이름으로 변경 + +class FirestoreTests: XCTestCase { + + + var repository: CommunityRepository! + + override func setUp() { + super.setUp() + repository = CommunityRepository() + } + + override func tearDown() { + repository = nil + super.tearDown() + } + + private let testPostId = "4B7F5702-EF0C-4C51-95F9-4CDCA499A58B" + private let testAnswerId = "94F96B2B-71AC-4616-95AD-545EF80F65C1" + private let testCommentId = "C644F4BF-5F32-4F67-B2D8-18F182A1FB2C" + + +//MARK: - 글 관련 테스트 + + // 모든 글 가져오기 테스트 + func testGetPosts() { + let expectation = self.expectation(description: "getPosts") + + repository.getPosts { posts in + + print("\n") + + for post in posts { + print(post) + } + + print("\n") + + XCTAssertNotNil(posts, "Should not return nil") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 무한스크롤 글 가져오기 함수 테스트 + func testGetPosts2() { + let expectation = self.expectation(description: "getPosts") + let limit = 10 // + let lastCreatedAt: String? = "20240411102228" + + repository.getPosts(limit: limit, lastCreatedAt: lastCreatedAt) { posts in + + print("\n") + + for post in posts { + print(post) + } + + print("\n") + + XCTAssertNotNil(posts, "Should not return nil") + + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + + // 글 생성 테스트 + func testCreatePost() { + let expectation = self.expectation(description: "createPost") + + let newPost = Post(type: PostType.General, content: "테스트용", author:"1") + + repository.createPost(post: newPost) { success in + + XCTAssertTrue(success, "Post Create should succed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 글 업데이트 테스트 + func testUpdatePost() { + let expectation = self.expectation(description: "updatePost") + + let postid = testPostId + repository.updatePost(postid: postid, content: "Updated Content", shortcuts: ["Shortcut1"], images: ["ImageURL1"]) { success in + XCTAssertTrue(success, "Post update should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 글 삭제 테스트 + func testDeletePost() { + let expectation = self.expectation(description: "deletePost") + + let postId = testPostId + repository.deletePost(postId: postId) { success in + XCTAssertTrue(success, "Post deletion should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + + // 글에 좋아요 추가 테스트 + func testLikePost() { + let expectation = self.expectation(description: "likePost") + + let postId = testPostId + let userId = "2" + + repository.likePost(postId: postId, userId: userId) { success in + XCTAssertTrue(success, "Liking a post should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 글에 좋아요 제거 테스트 + func testUnlikePost() { + let expectation = self.expectation(description: "unlikePost") + + let postId = testPostId + let userId = "2" + + repository.unlikePost(postId: postId, userId: userId) { success in + XCTAssertTrue(success, "Unliking a post should succeed.") + expectation.fulfill() + } + + + waitForExpectations(timeout: 5, handler: nil) + } + +//MARK: - 답변 관련 테스트 + + // 특정 게시물의 모든 답변을 가져오는 함수 테스트 + func testGetAnswers() { + let expectation = self.expectation(description: "getAnswers") + let testPostId = testPostId + + repository.getAnswers(for: testPostId) { answers in + + print("\n") + + for answer in answers { + print(answer) + } + + print("\n") + + XCTAssertNotNil(answers, "Should not return nil") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 새로운 답변을 생성하는 함수 테스트 + func testCreateAnswer() { + let expectation = self.expectation(description: "createAnswer") + let answer = Answer(content: "Test answer", author: "1", postId: testPostId) + + repository.createAnswer(answer: answer) { success in + XCTAssertTrue(success, "Answer creation should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 기존의 답변을 업데이트하는 함수 테스트 + func testUpdateAnswer() { + let expectation = self.expectation(description: "updateAnswer") + let answerId = testAnswerId + + repository.updateAnswer(answerId: answerId, content: "Updated content", images: ["UpdatedImageURL"]) { success in + XCTAssertTrue(success, "Answer update should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + + // 답변을 채택하는 함수 테스트 + func testAcceptAnswer() { + let expectation = self.expectation(description: "acceptAnswer") + let answerId = testAnswerId + + repository.acceptAnswer(answerId: answerId) { success in + XCTAssertTrue(success, "Answer acception should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 특정 답변을 삭제하는 함수 테스트 + func testDeleteAnswer() { + let expectation = self.expectation(description: "deleteAnswer") + let answerId = testAnswerId + + repository.deleteAnswer(answerId: answerId) { success in + XCTAssertTrue(success, "Answer deletion should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + +//MARK: - 댓글 관련 테스트 + + // 특정 게시물의 모든 댓글을 가져오는 함수 테스트 + func testGetComments() { + let expectation = self.expectation(description: "getComments") + let testPostId = testPostId + + repository.getComments(postId: testPostId) { comments in + + print("\n") + + for comment in comments { + print(comment) + } + + print("\n") + + XCTAssertNotNil(comments, "Should not return nil") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 새로운 댓글을 생성하는 함수 테스트 + func testCreateComment() { + let expectation = self.expectation(description: "createComment") + let comment = CommunityComment(content: "Test comment", author: "1", postId: testPostId) + + repository.createComment(comment: comment) { success in + XCTAssertTrue(success, "Comment creation should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 기존의 댓글을 업데이트하는 함수 테스트 + func testUpdateComment() { + let expectation = self.expectation(description: "updateComment") + let commentId = testCommentId + let newContent = "Updated content" + + repository.updateComment(commentId: commentId, content: newContent) { success in + XCTAssertTrue(success, "Comment update should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 특정 댓글을 삭제하는 함수 테스트 + func testDeleteComment() { + let expectation = self.expectation(description: "deleteComment") + let commentId = testCommentId + + repository.deleteComment(commentId: commentId) { success in + XCTAssertTrue(success, "Comment deletion should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 댓글에 좋아요를 추가하는 함수 테스트 + func testLikeComment() { + let expectation = self.expectation(description: "likeComment") + let commentId = testCommentId + let userId = "1" + + repository.likeComment(commentId: commentId, userId: userId) { success in + XCTAssertTrue(success, "Liking a comment should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 댓글에 추가된 좋아요를 제거하는 함수 테스트 + func testUnlikeComment() { + let expectation = self.expectation(description: "unlikeComment") + let commentId = testCommentId + let userId = "1" + + repository.unlikeComment(commentId: commentId, userId: userId) { success in + XCTAssertTrue(success, "Unliking a comment should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + + +} From 35cc8f935c6cdd62751ca34f9556755707c9e294 Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 12 Apr 2024 10:36:44 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[Feat]=20#520=20-=20=EB=8B=B5=EB=B3=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/CommunityRepository.swift | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift index e60b4104..c7c4fb3f 100644 --- a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift +++ b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift @@ -307,6 +307,69 @@ class CommunityRepository { } } } + + func likeAnswer(answerId: String, userId: String, completion: @escaping (Bool) -> Void) { + let answerRef = db.collection(answerCollection).document(answerId) + + answerRef.getDocument { (document, error) in + if let document = document, let data = document.data(), error == nil { + var likedBy = data["likedBy"] as? [String: Bool] ?? [:] + + if likedBy[userId] != true { + likedBy[userId] = true + + let likeCount = (data["likeCount"] as? Int ?? 0) + 1 + + answerRef.updateData([ + "likedBy": likedBy, + "likeCount": likeCount + ]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } + } else { + completion(false) + } + } + } + + func unlikeAnswer(answerId: String, userId: String, completion: @escaping (Bool) -> Void) { + let answerRef = db.collection(answerCollection).document(answerId) + + answerRef.getDocument { documentSnapshot, error in + guard let document = documentSnapshot, let data = document.data(), error == nil else { + completion(false) + return + } + + var likedBy = data["likedBy"] as? [String: Bool] ?? [:] + + if likedBy[userId] == true { + likedBy.removeValue(forKey: userId) + + let likeCount = max((data["likeCount"] as? Int ?? 0) - 1, 0) + + answerRef.updateData([ + "likedBy": likedBy, + "likeCount": likeCount + ]) { error in + if error != nil { + completion(false) + } else { + completion(true) + } + } + } else { + completion(true) + } + } + } //MARK: - 댓글 관련 함수 From f5195a17608a0c65612b416cf64261b48cae1ea9 Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 12 Apr 2024 10:37:07 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[Test]=20#520=20-=20=EB=8B=B5=EB=B3=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommunityRepositoryTest.swift | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift b/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift index 647b6308..f38c5fd4 100644 --- a/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift +++ b/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift @@ -24,7 +24,7 @@ class FirestoreTests: XCTestCase { } private let testPostId = "4B7F5702-EF0C-4C51-95F9-4CDCA499A58B" - private let testAnswerId = "94F96B2B-71AC-4616-95AD-545EF80F65C1" + private let testAnswerId = "E0B83A68-9536-4976-BD64-41DCB21DB304" private let testCommentId = "C644F4BF-5F32-4F67-B2D8-18F182A1FB2C" @@ -226,6 +226,34 @@ class FirestoreTests: XCTestCase { waitForExpectations(timeout: 5, handler: nil) } + // 답변에 좋아요를 누르는 함수 테스트 + func testLikeAnswer() { + let expectation = self.expectation(description: "likeAnswer") + let answerId = testAnswerId + let userId = "1" + + repository.likeAnswer(answerId: answerId, userId: userId) { success in + XCTAssertTrue(success, "Liking an answer should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 답변에 좋아요를 제거하는 함수 테스트 + func testUnlikeAnswer() { + let expectation = self.expectation(description: "unlikeAnswer") + let answerId = testAnswerId + let userId = "1" + + repository.unlikeAnswer(answerId: answerId, userId: userId) { success in + XCTAssertTrue(success, "Unliking an answer should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + //MARK: - 댓글 관련 테스트 // 특정 게시물의 모든 댓글을 가져오는 함수 테스트 From 5905422fadd2c6e141fbebc39aedced9fe079a72 Mon Sep 17 00:00:00 2001 From: YJU Date: Thu, 18 Apr 2024 20:44:47 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[Feat]=20#520=20-=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=B0=8F=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HappyAnding/HappyAnding/Model/Answer.swift | 4 +- HappyAnding/HappyAnding/Model/Post.swift | 4 +- .../Repository/CommunityRepository.swift | 486 ++++++++++++++++-- 3 files changed, 446 insertions(+), 48 deletions(-) diff --git a/HappyAnding/HappyAnding/Model/Answer.swift b/HappyAnding/HappyAnding/Model/Answer.swift index 41d3fd8c..188ab76f 100644 --- a/HappyAnding/HappyAnding/Model/Answer.swift +++ b/HappyAnding/HappyAnding/Model/Answer.swift @@ -17,10 +17,11 @@ struct Answer: Identifiable, Codable, Equatable, Hashable { var content: String var isAccepted: Bool var images: [String] + var thumbnailImages: [String] var likedBy: [String:Bool] var likeCount: Int - init(content: String, author: String, postId:String, images: [String] = []) { + init(content: String, author: String, postId:String, images: [String] = [], thumbnailImages: [String] = []) { self.id = UUID().uuidString self.createdAt = Date().getDate() @@ -30,6 +31,7 @@ struct Answer: Identifiable, Codable, Equatable, Hashable { self.author = author self.postId = postId self.images = images + self.thumbnailImages = thumbnailImages self.likeCount = 0 self.likedBy = [:] diff --git a/HappyAnding/HappyAnding/Model/Post.swift b/HappyAnding/HappyAnding/Model/Post.swift index 5076be8f..7957dc6b 100644 --- a/HappyAnding/HappyAnding/Model/Post.swift +++ b/HappyAnding/HappyAnding/Model/Post.swift @@ -17,11 +17,12 @@ struct Post: Identifiable, Codable, Equatable, Hashable { var content: String var shortcuts: [String] var images: [String] + var thumbnailImages: [String] var likedBy: [String:Bool] var likeCount: Int var commentCount: Int - init(type: PostType, content: String, author: String, shortcuts: [String] = [], images: [String] = []) { + init(type: PostType, content: String, author: String, shortcuts: [String] = [], images: [String] = [], thumbnailImages: [String] = []) { self.id = UUID().uuidString self.createdAt = Date().getDate() @@ -31,6 +32,7 @@ struct Post: Identifiable, Codable, Equatable, Hashable { self.author = author self.shortcuts = shortcuts self.images = images + self.thumbnailImages = thumbnailImages self.likeCount = 0 self.commentCount = 0 diff --git a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift index c7c4fb3f..8e8b05ee 100644 --- a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift +++ b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift @@ -8,19 +8,92 @@ import Foundation import FirebaseCore import FirebaseFirestore +import FirebaseStorage import FirebaseFirestoreSwift import FirebaseAuth - +import SwiftUI class CommunityRepository { private let db = Firestore.firestore() + private let storage = Storage.storage().reference() private let postCollection: String = "Post" private let communityCommentCollection: String = "CommunityComment" private let answerCollection: String = "Answer" + + private func uploadImages(images: [UIImage], completion: @escaping ([String]?) -> Void) { + var imageURLs = [String]() + let imageUploadGroup = DispatchGroup() + + for image in images { + imageUploadGroup.enter() + let imageData = image.pngData()! + let imageId = UUID().uuidString + let imageRef = storage.child("community/\(imageId).png") + + imageRef.putData(imageData, metadata: nil) { metadata, error in + if let error = error { + print(error.localizedDescription) + imageUploadGroup.leave() + return + } + + imageRef.downloadURL { (url, error) in + if let error = error { + print(error.localizedDescription) + imageUploadGroup.leave() + return + } + + if let downloadURL = url { + imageURLs.append(downloadURL.absoluteString) + imageUploadGroup.leave() + } + } + } + } + + imageUploadGroup.notify(queue: .main) { + if imageURLs.isEmpty { + completion(nil) + } else { + completion(imageURLs) + } + } + } + + + private func deleteImages(with urls: [String], completion: @escaping (Bool) -> Void) { + let storage = Storage.storage() + + // 카운터를 사용하여 모든 삭제 작업이 완료되었는지 추적합니다. + var deleteCount = 0 + var deleteErrors = false + + for url in urls { + // URL로부터 참조를 얻습니다. + let ref = storage.reference(forURL: url) + + // 참조를 사용하여 이미지 삭제 + ref.delete { error in + if let error = error { + deleteErrors = true + } + + deleteCount += 1 + if deleteCount == urls.count { + completion(!deleteErrors) + } + } + } + } + + + + @@ -28,7 +101,7 @@ class CommunityRepository { // 모든 글 가져오기 - func getPosts(completion: @escaping ([Post]) -> Void) { + func getAllPosts(completion: @escaping ([Post]) -> Void) { db.collection(postCollection) .order(by: "createdAt", descending: true) // 최신 글부터 정렬 .getDocuments { snapshot, error in @@ -69,54 +142,207 @@ class CommunityRepository { // 글 생성 - func createPost(post: Post, completion: @escaping (Bool) -> Void) { +// func createPost(post: Post, completion: @escaping (Bool) -> Void) { +// let documentId = post.id +// do { +// try db.collection(postCollection).document(documentId).setData(from: post) { error in +// if error != nil { +// completion(false) +// } else { +// completion(true) +// } +// } +// } catch { +// completion(false) +// } +// } +// + + // 글 생성 with images + func createPost(post: Post, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { let documentId = post.id + do { try db.collection(postCollection).document(documentId).setData(from: post) { error in - if error != nil { + if let error = error { completion(false) - } else { - completion(true) + return + } + + var imageURLs: [String] = [] + var thumbnailURLs: [String] = [] + let dispatchGroup = DispatchGroup() + + // 일반 이미지 업로드 + if let images = images, !images.isEmpty { + dispatchGroup.enter() + self.uploadImages(images: images) { urls in + if let urls = urls { + imageURLs = urls + } + dispatchGroup.leave() + } + } + + // 썸네일 이미지 업로드 + if let thumbnails = thumbnailImages, !thumbnails.isEmpty { + dispatchGroup.enter() + self.uploadImages(images: thumbnails) { urls in + if let urls = urls { + thumbnailURLs = urls + } + dispatchGroup.leave() + } + } + + // 모든 이미지 업로드가 완료된 후 Firestore 문서 업데이트 + dispatchGroup.notify(queue: .main) { + var updateData = [String: Any]() + if !imageURLs.isEmpty { + updateData["images"] = imageURLs + } + if !thumbnailURLs.isEmpty { + updateData["thumbnailImages"] = thumbnailURLs + } + + if !updateData.isEmpty { + self.db.collection(self.postCollection).document(documentId).updateData(updateData) { error in + if let error = error { + completion(false) + } else { + completion(true) + } + } + } else { + // 업데이트할 데이터가 없으면 성공으로 처리 + completion(true) + } } } } catch { completion(false) } } + - // 글 업데이트 - func updatePost(postid: String, content: String? = nil, shortcuts: [String]? = nil, images: [String]? = nil, completion: @escaping (Bool) -> Void) { +// // 글 업데이트 +// func updatePost(postid: String, content: String? = nil, shortcuts: [String]? = nil, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { +// +// var updateFields: [String: Any] = [:] +// +// if let content = content { +// updateFields["content"] = content +// } +// if let shortcuts = shortcuts { +// updateFields["shortcuts"] = shortcuts +// } +// +// if !updateFields.isEmpty { +// db.collection(postCollection).document(postid).updateData(updateFields) { error in +// if error != nil { +// completion(false) +// } else { +// completion(true) +// } +// } +// } else { +// completion(true) +// } +// } + func updatePost(postId: String, content: String? = nil, shortcuts: [String]? = nil, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { + let db = Firestore.firestore() + let documentRef = db.collection("posts").document(postId) + var updateFields: [String: Any] = [:] - if let content = content { updateFields["content"] = content } if let shortcuts = shortcuts { updateFields["shortcuts"] = shortcuts } - if let images = images { - updateFields["images"] = images - } - if !updateFields.isEmpty { - db.collection(postCollection).document(postid).updateData(updateFields) { error in - if error != nil { - completion(false) + documentRef.updateData(updateFields) { error in + if let error = error { + completion(false) + return + } + + // 이미지 업데이트 로직 + guard images != nil || thumbnailImages != nil else { + completion(true) + return + } + + // 이미지 삭제 및 업로드 + documentRef.getDocument { document, error in + if let document = document, document.exists { + let oldImageUrls = document.data()?["images"] as? [String] ?? [] + let oldThumbnailUrls = document.data()?["thumbnailImages"] as? [String] ?? [] + + self.deleteImages(with: oldImageUrls + oldThumbnailUrls) { success in + if !success { + completion(false) + return + } + + // 새 이미지와 썸네일 이미지 업로드 + self.uploadImages(images: images ?? []) { newImageUrls in + self.uploadImages(images: thumbnailImages ?? []) { newThumbnailUrls in + var imageUpdateFields: [String: Any] = [:] + if let newImageUrls = newImageUrls { + imageUpdateFields["images"] = newImageUrls + } + if let newThumbnailUrls = newThumbnailUrls { + imageUpdateFields["thumbnailImages"] = newThumbnailUrls + } + + if !imageUpdateFields.isEmpty { + documentRef.updateData(imageUpdateFields) { error in + completion(error == nil) + } + } else { + completion(true) + } + } + } + } } else { - completion(true) + completion(false) } } - } else { - completion(true) } } + + + // 글 삭제 func deletePost(postId: String, completion: @escaping (Bool) -> Void) { let group = DispatchGroup() var overallSuccess = true + // 게시물의 이미지 URL을 가져오고 삭제 + group.enter() + db.collection(postCollection).document(postId).getDocument { document, error in + if let document = document, document.exists { + let imageUrls = document.data()?["images"] as? [String] ?? [] + let thumbnailUrls = document.data()?["thumbnailImages"] as? [String] ?? [] + let allUrls = imageUrls + thumbnailUrls + + // 이미지 삭제 로직 + for url in allUrls { + let ref = Storage.storage().reference(forURL: url) + ref.delete { error in + if error != nil { + overallSuccess = false + } + } + } + } + group.leave() + } + // 게시물 삭제 group.enter() db.collection(postCollection).document(postId).delete { error in @@ -244,14 +470,75 @@ class CommunityRepository { } } - func createAnswer(answer: Answer, completion: @escaping (Bool) -> Void) { - let documentId = answer.id +// func createAnswer(answer: Answer, completion: @escaping (Bool) -> Void) { +// let documentId = answer.id +// do { +// try db.collection(answerCollection).document(documentId).setData(from: answer) { error in +// if error != nil { +// completion(false) +// } else { +// completion(true) +// } +// } +// } catch { +// completion(false) +// } +// } + + func createAnswer(answer: Answer, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { + let documentId = answer.id // Answer 객체의 ID를 사용 + do { try db.collection(answerCollection).document(documentId).setData(from: answer) { error in - if error != nil { + if let error = error { completion(false) - } else { - completion(true) + return + } + + var imageURLs: [String] = [] + var thumbnailURLs: [String] = [] + let dispatchGroup = DispatchGroup() + + // 일반 이미지 업로드 + if let images = images, !images.isEmpty { + dispatchGroup.enter() + self.uploadImages(images: images) { urls in + if let urls = urls { + imageURLs = urls + } + dispatchGroup.leave() + } + } + + // 썸네일 이미지 업로드 + if let thumbnails = thumbnailImages, !thumbnails.isEmpty { + dispatchGroup.enter() + self.uploadImages(images: thumbnails) { urls in + if let urls = urls { + thumbnailURLs = urls + } + dispatchGroup.leave() + } + } + + // 모든 이미지 업로드가 완료된 후 Firestore 문서 업데이트 + dispatchGroup.notify(queue: .main) { + var updateData = [String: Any]() + if !imageURLs.isEmpty { + updateData["images"] = imageURLs + } + if !thumbnailURLs.isEmpty { + updateData["thumbnailImages"] = thumbnailURLs + } + + if !updateData.isEmpty { + self.db.collection(self.answerCollection).document(documentId).updateData(updateData) { error in + completion(error == nil) + } + } else { + // 업데이트할 데이터가 없으면 성공으로 처리 + completion(true) + } } } } catch { @@ -259,28 +546,92 @@ class CommunityRepository { } } - func updateAnswer(answerId: String, content: String? = nil, images: [String]? = nil, completion: @escaping (Bool) -> Void) { - +// func updateAnswer(answerId: String, content: String? = nil, images: [String]? = nil, completion: @escaping (Bool) -> Void) { +// +// var updateFields: [String: Any] = [:] +// +// if let newContent = content { +// updateFields["content"] = newContent +// } +// +// if let newImages = images { +// updateFields["images"] = newImages +// } +// +// if !updateFields.isEmpty { +// db.collection(answerCollection).document(answerId).updateData(updateFields) { error in +// if error != nil { +// completion(false) +// } else { +// completion(true) +// } +// } +// } else { +// completion(true) +// } +// } + + + + + func updateAnswer(answerId: String, content: String? = nil, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { + let db = Firestore.firestore() + let documentRef = db.collection(answerCollection).document(answerId) + var updateFields: [String: Any] = [:] - - if let newContent = content { - updateFields["content"] = newContent - } - - if let newImages = images { - updateFields["images"] = newImages + if let content = content { + updateFields["content"] = content } - if !updateFields.isEmpty { - db.collection(answerCollection).document(answerId).updateData(updateFields) { error in - if error != nil { - completion(false) + documentRef.updateData(updateFields) { error in + if let error = error { + completion(false) + return + } + + // 이미지 업데이트 로직 + guard images != nil || thumbnailImages != nil else { + completion(true) + return + } + + // 이미지 삭제 및 업로드 + documentRef.getDocument { document, error in + if let document = document, document.exists { + let oldImageUrls = document.data()?["images"] as? [String] ?? [] + let oldThumbnailUrls = document.data()?["thumbnailImages"] as? [String] ?? [] + + self.deleteImages(with: oldImageUrls + oldThumbnailUrls) { success in + if !success { + completion(false) + return + } + + // 새 이미지와 썸네일 이미지 업로드 + self.uploadImages(images: images ?? []) { newImageUrls in + self.uploadImages(images: thumbnailImages ?? []) { newThumbnailUrls in + var imageUpdateFields: [String: Any] = [:] + if let newImageUrls = newImageUrls { + imageUpdateFields["images"] = newImageUrls + } + if let newThumbnailUrls = newThumbnailUrls { + imageUpdateFields["thumbnailImages"] = newThumbnailUrls + } + + if !imageUpdateFields.isEmpty { + documentRef.updateData(imageUpdateFields) { error in + completion(error == nil) + } + } else { + completion(true) + } + } + } + } } else { - completion(true) + completion(false) } } - } else { - completion(true) } } @@ -297,15 +648,58 @@ class CommunityRepository { } +// func deleteAnswer(answerId: String, completion: @escaping (Bool) -> Void) { +// db.collection(answerCollection).document(answerId) +// .delete() { error in +// if error != nil { +// completion(false) +// } else { +// completion(true) +// } +// } +// } + + + func deleteAnswer(answerId: String, completion: @escaping (Bool) -> Void) { - db.collection(answerCollection).document(answerId) - .delete() { error in - if error != nil { - completion(false) - } else { - completion(true) + let db = Firestore.firestore() + let group = DispatchGroup() + var overallSuccess = true + + // 답변의 이미지 URL을 가져오고 삭제 + group.enter() + db.collection(answerCollection).document(answerId).getDocument { document, error in + if let document = document, document.exists { + let imageUrls = document.data()?["images"] as? [String] ?? [] + let thumbnailUrls = document.data()?["thumbnailImages"] as? [String] ?? [] + let allUrls = imageUrls + thumbnailUrls + + // 이미지 삭제 로직 + for url in allUrls { + let ref = Storage.storage().reference(forURL: url) + ref.delete { error in + if error != nil { + overallSuccess = false + } + } } } + group.leave() + } + + // 답변 삭제 + group.enter() + db.collection(answerCollection).document(answerId).delete { error in + if error != nil { + overallSuccess = false + } + group.leave() + } + + // 모든 삭제 작업이 완료되었는지 확인 + group.notify(queue: .main) { + completion(overallSuccess) + } } func likeAnswer(answerId: String, userId: String, completion: @escaping (Bool) -> Void) { From b2541382ce7abe7347e39d055b6654b8973b77f3 Mon Sep 17 00:00:00 2001 From: YJU Date: Sun, 21 Apr 2024 17:07:30 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[Feat]=20#520=20-=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HappyAnding/HappyAnding/Model/Answer.swift | 6 +- HappyAnding/HappyAnding/Model/Post.swift | 6 +- .../Repository/CommunityRepository.swift | 226 ++++++------------ .../CommunityRepositoryTest.swift | 67 +++++- 4 files changed, 130 insertions(+), 175 deletions(-) diff --git a/HappyAnding/HappyAnding/Model/Answer.swift b/HappyAnding/HappyAnding/Model/Answer.swift index 188ab76f..077f3cea 100644 --- a/HappyAnding/HappyAnding/Model/Answer.swift +++ b/HappyAnding/HappyAnding/Model/Answer.swift @@ -21,7 +21,7 @@ struct Answer: Identifiable, Codable, Equatable, Hashable { var likedBy: [String:Bool] var likeCount: Int - init(content: String, author: String, postId:String, images: [String] = [], thumbnailImages: [String] = []) { + init(content: String, author: String, postId:String) { self.id = UUID().uuidString self.createdAt = Date().getDate() @@ -30,8 +30,8 @@ struct Answer: Identifiable, Codable, Equatable, Hashable { self.isAccepted = false self.author = author self.postId = postId - self.images = images - self.thumbnailImages = thumbnailImages + self.images = [] + self.thumbnailImages = [] self.likeCount = 0 self.likedBy = [:] diff --git a/HappyAnding/HappyAnding/Model/Post.swift b/HappyAnding/HappyAnding/Model/Post.swift index 7957dc6b..2406b098 100644 --- a/HappyAnding/HappyAnding/Model/Post.swift +++ b/HappyAnding/HappyAnding/Model/Post.swift @@ -22,7 +22,7 @@ struct Post: Identifiable, Codable, Equatable, Hashable { var likeCount: Int var commentCount: Int - init(type: PostType, content: String, author: String, shortcuts: [String] = [], images: [String] = [], thumbnailImages: [String] = []) { + init(type: PostType, content: String, author: String, shortcuts: [String] = []) { self.id = UUID().uuidString self.createdAt = Date().getDate() @@ -31,8 +31,8 @@ struct Post: Identifiable, Codable, Equatable, Hashable { self.content = content self.author = author self.shortcuts = shortcuts - self.images = images - self.thumbnailImages = thumbnailImages + self.images = [] + self.thumbnailImages = [] self.likeCount = 0 self.commentCount = 0 diff --git a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift index 8e8b05ee..bbeb176d 100644 --- a/HappyAnding/HappyAnding/Repository/CommunityRepository.swift +++ b/HappyAnding/HappyAnding/Repository/CommunityRepository.swift @@ -33,8 +33,11 @@ class CommunityRepository { let imageData = image.pngData()! let imageId = UUID().uuidString let imageRef = storage.child("community/\(imageId).png") + + let metadata = StorageMetadata() + metadata.contentType = "image/png" - imageRef.putData(imageData, metadata: nil) { metadata, error in + imageRef.putData(imageData, metadata: metadata) { metadata, error in if let error = error { print(error.localizedDescription) imageUploadGroup.leave() @@ -68,32 +71,30 @@ class CommunityRepository { private func deleteImages(with urls: [String], completion: @escaping (Bool) -> Void) { let storage = Storage.storage() - - // 카운터를 사용하여 모든 삭제 작업이 완료되었는지 추적합니다. - var deleteCount = 0 + let dispatchGroup = DispatchGroup() + var deleteErrors = false for url in urls { - // URL로부터 참조를 얻습니다. let ref = storage.reference(forURL: url) - // 참조를 사용하여 이미지 삭제 + dispatchGroup.enter() + ref.delete { error in if let error = error { deleteErrors = true } - - deleteCount += 1 - if deleteCount == urls.count { - completion(!deleteErrors) - } + dispatchGroup.leave() } } + + dispatchGroup.notify(queue: .main) { + completion(!deleteErrors) + } } - @@ -140,31 +141,15 @@ class CommunityRepository { } } - - // 글 생성 -// func createPost(post: Post, completion: @escaping (Bool) -> Void) { -// let documentId = post.id -// do { -// try db.collection(postCollection).document(documentId).setData(from: post) { error in -// if error != nil { -// completion(false) -// } else { -// completion(true) -// } -// } -// } catch { -// completion(false) -// } -// } -// - - // 글 생성 with images + + // 글 생성 func createPost(post: Post, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { let documentId = post.id do { try db.collection(postCollection).document(documentId).setData(from: post) { error in if let error = error { + print(error.localizedDescription) completion(false) return } @@ -172,6 +157,7 @@ class CommunityRepository { var imageURLs: [String] = [] var thumbnailURLs: [String] = [] let dispatchGroup = DispatchGroup() + // 일반 이미지 업로드 if let images = images, !images.isEmpty { @@ -208,13 +194,13 @@ class CommunityRepository { if !updateData.isEmpty { self.db.collection(self.postCollection).document(documentId).updateData(updateData) { error in if let error = error { + print(error.localizedDescription) completion(false) } else { completion(true) } } } else { - // 업데이트할 데이터가 없으면 성공으로 처리 completion(true) } } @@ -224,35 +210,10 @@ class CommunityRepository { } } - -// // 글 업데이트 -// func updatePost(postid: String, content: String? = nil, shortcuts: [String]? = nil, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { -// -// var updateFields: [String: Any] = [:] -// -// if let content = content { -// updateFields["content"] = content -// } -// if let shortcuts = shortcuts { -// updateFields["shortcuts"] = shortcuts -// } -// -// if !updateFields.isEmpty { -// db.collection(postCollection).document(postid).updateData(updateFields) { error in -// if error != nil { -// completion(false) -// } else { -// completion(true) -// } -// } -// } else { -// completion(true) -// } -// } - + // 글 업데이트 func updatePost(postId: String, content: String? = nil, shortcuts: [String]? = nil, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { - let db = Firestore.firestore() - let documentRef = db.collection("posts").document(postId) + + let documentRef = db.collection(postCollection).document(postId) var updateFields: [String: Any] = [:] if let content = content { @@ -264,11 +225,12 @@ class CommunityRepository { documentRef.updateData(updateFields) { error in if let error = error { + print(error.localizedDescription) completion(false) return } + - // 이미지 업데이트 로직 guard images != nil || thumbnailImages != nil else { completion(true) return @@ -322,36 +284,37 @@ class CommunityRepository { let group = DispatchGroup() var overallSuccess = true - // 게시물의 이미지 URL을 가져오고 삭제 + let documentRef = db.collection(postCollection).document(postId) + group.enter() - db.collection(postCollection).document(postId).getDocument { document, error in + documentRef.getDocument { document, error in if let document = document, document.exists { let imageUrls = document.data()?["images"] as? [String] ?? [] let thumbnailUrls = document.data()?["thumbnailImages"] as? [String] ?? [] let allUrls = imageUrls + thumbnailUrls - - // 이미지 삭제 로직 - for url in allUrls { - let ref = Storage.storage().reference(forURL: url) - ref.delete { error in - if error != nil { + if !allUrls.isEmpty { + self.deleteImages(with: allUrls) { success in + if !success { overallSuccess = false } + group.leave() } + } else { + group.leave() } - } - group.leave() - } - - // 게시물 삭제 - group.enter() - db.collection(postCollection).document(postId).delete { error in - if error != nil { + + } else { overallSuccess = false } - group.leave() + + documentRef.delete { error in + if error != nil { + overallSuccess = false + } + } } + // 관련된 답변(Answer) 삭제 group.enter() db.collection(answerCollection).whereField("postId", isEqualTo: postId).getDocuments { (snapshot, error) in @@ -361,7 +324,11 @@ class CommunityRepository { return } for document in documents { - document.reference.delete() + self.deleteAnswer(answerId:document.data()["id"] as? String ?? "") { success in + if (!success) { + overallSuccess = false + } + } } group.leave() } @@ -380,7 +347,6 @@ class CommunityRepository { group.leave() } - // 모든 삭제 작업이 완료되었는지 확인 group.notify(queue: .main) { completion(overallSuccess) } @@ -470,21 +436,7 @@ class CommunityRepository { } } -// func createAnswer(answer: Answer, completion: @escaping (Bool) -> Void) { -// let documentId = answer.id -// do { -// try db.collection(answerCollection).document(documentId).setData(from: answer) { error in -// if error != nil { -// completion(false) -// } else { -// completion(true) -// } -// } -// } catch { -// completion(false) -// } -// } - + // 답변 생성 func createAnswer(answer: Answer, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { let documentId = answer.id // Answer 객체의 ID를 사용 @@ -536,7 +488,6 @@ class CommunityRepository { completion(error == nil) } } else { - // 업데이트할 데이터가 없으면 성공으로 처리 completion(true) } } @@ -546,36 +497,10 @@ class CommunityRepository { } } -// func updateAnswer(answerId: String, content: String? = nil, images: [String]? = nil, completion: @escaping (Bool) -> Void) { -// -// var updateFields: [String: Any] = [:] -// -// if let newContent = content { -// updateFields["content"] = newContent -// } -// -// if let newImages = images { -// updateFields["images"] = newImages -// } -// -// if !updateFields.isEmpty { -// db.collection(answerCollection).document(answerId).updateData(updateFields) { error in -// if error != nil { -// completion(false) -// } else { -// completion(true) -// } -// } -// } else { -// completion(true) -// } -// } - - - - + + // 답변 업데이트 func updateAnswer(answerId: String, content: String? = nil, images: [UIImage]? = nil, thumbnailImages: [UIImage]? = nil, completion: @escaping (Bool) -> Void) { - let db = Firestore.firestore() + let documentRef = db.collection(answerCollection).document(answerId) var updateFields: [String: Any] = [:] @@ -635,6 +560,7 @@ class CommunityRepository { } } + // 답변 채택 func acceptAnswer(answerId: String, completion: @escaping (Bool) -> Void) { let answerRef = db.collection(answerCollection).document(answerId) @@ -647,26 +573,12 @@ class CommunityRepository { } } - -// func deleteAnswer(answerId: String, completion: @escaping (Bool) -> Void) { -// db.collection(answerCollection).document(answerId) -// .delete() { error in -// if error != nil { -// completion(false) -// } else { -// completion(true) -// } -// } -// } - - - + // 답변 삭제 func deleteAnswer(answerId: String, completion: @escaping (Bool) -> Void) { let db = Firestore.firestore() let group = DispatchGroup() var overallSuccess = true - // 답변의 이미지 URL을 가져오고 삭제 group.enter() db.collection(answerCollection).document(answerId).getDocument { document, error in if let document = document, document.exists { @@ -674,34 +586,34 @@ class CommunityRepository { let thumbnailUrls = document.data()?["thumbnailImages"] as? [String] ?? [] let allUrls = imageUrls + thumbnailUrls - // 이미지 삭제 로직 - for url in allUrls { - let ref = Storage.storage().reference(forURL: url) - ref.delete { error in - if error != nil { + if !allUrls.isEmpty { + self.deleteImages(with: allUrls) { success in + if !success { overallSuccess = false } + group.leave() } + } else { + group.leave() } - } - group.leave() - } - - // 답변 삭제 - group.enter() - db.collection(answerCollection).document(answerId).delete { error in - if error != nil { + } else { overallSuccess = false + group.leave() + } + + db.collection(self.answerCollection).document(answerId).delete { error in + if error != nil { + overallSuccess = false + } } - group.leave() } - // 모든 삭제 작업이 완료되었는지 확인 group.notify(queue: .main) { completion(overallSuccess) } } + // 답변 좋아요 func likeAnswer(answerId: String, userId: String, completion: @escaping (Bool) -> Void) { let answerRef = db.collection(answerCollection).document(answerId) @@ -733,6 +645,8 @@ class CommunityRepository { } } + + // 답변 좋아요 취소 func unlikeAnswer(answerId: String, userId: String, completion: @escaping (Bool) -> Void) { let answerRef = db.collection(answerCollection).document(answerId) @@ -822,8 +736,6 @@ class CommunityRepository { if !updates.isEmpty { db.collection(communityCommentCollection).document(commentId).updateData(updates) { error in if let error = error { - print("안녕") - print(error) completion(false) } else { completion(true) diff --git a/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift b/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift index f38c5fd4..26789236 100644 --- a/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift +++ b/HappyAnding/HappyAndingTests/CommunityRepositoryTest.swift @@ -23,18 +23,20 @@ class FirestoreTests: XCTestCase { super.tearDown() } - private let testPostId = "4B7F5702-EF0C-4C51-95F9-4CDCA499A58B" - private let testAnswerId = "E0B83A68-9536-4976-BD64-41DCB21DB304" + private let testPostId = "8F242D05-3FB4-4604-880A-93A99B3F77AF" + private let testAnswerId = "CECD1EBA-2157-41ED-AA24-AEBD4D105272" private let testCommentId = "C644F4BF-5F32-4F67-B2D8-18F182A1FB2C" //MARK: - 글 관련 테스트 + + // 모든 글 가져오기 테스트 - func testGetPosts() { + func testGetAllPosts() { let expectation = self.expectation(description: "getPosts") - repository.getPosts { posts in + repository.getAllPosts { posts in print("\n") @@ -50,11 +52,11 @@ class FirestoreTests: XCTestCase { waitForExpectations(timeout: 5, handler: nil) } - + // 무한스크롤 글 가져오기 함수 테스트 - func testGetPosts2() { + func testGetPosts() { let expectation = self.expectation(description: "getPosts") - let limit = 10 // + let limit = 10 let lastCreatedAt: String? = "20240411102228" repository.getPosts(limit: limit, lastCreatedAt: lastCreatedAt) { posts in @@ -91,12 +93,32 @@ class FirestoreTests: XCTestCase { waitForExpectations(timeout: 5, handler: nil) } - // 글 업데이트 테스트 + // 글 생성 테스트 with Images + func testCreatePostWithImages() { + let expectation = self.expectation(description: "Completion handler invoked") + + let testPost = Post(type:PostType.General, content: "This is a test post", author:"1") + let testImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + let testthumbnailImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + + repository.createPost(post: testPost, images: testImages, thumbnailImages: testthumbnailImages) { success in + + XCTAssertTrue(success, "Post Create should succed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 글 업데이트 테스트 func testUpdatePost() { let expectation = self.expectation(description: "updatePost") - let postid = testPostId - repository.updatePost(postid: postid, content: "Updated Content", shortcuts: ["Shortcut1"], images: ["ImageURL1"]) { success in + let testImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + let testthumbnailImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + + let postId = testPostId + repository.updatePost(postId: postId, content: "Updated Content", shortcuts: ["Shortcut1"], images:testImages, thumbnailImages: testthumbnailImages) { success in XCTAssertTrue(success, "Post update should succeed.") expectation.fulfill() } @@ -114,7 +136,7 @@ class FirestoreTests: XCTestCase { expectation.fulfill() } - waitForExpectations(timeout: 5, handler: nil) + waitForExpectations(timeout: 20, handler: nil) } @@ -185,13 +207,34 @@ class FirestoreTests: XCTestCase { waitForExpectations(timeout: 5, handler: nil) } + + // 새로운 답변을 생성하는 함수 with Images 테스트 + func testCreateAnswerWithImages() { + let expectation = self.expectation(description: "createAnswer") + let answer = Answer(content: "Test answer", author: "1", postId: testPostId) + + let testImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + let testthumbnailImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + + repository.createAnswer(answer: answer, images: testImages, thumbnailImages: testthumbnailImages) { success in + XCTAssertTrue(success, "Answer creation should succeed.") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + // 기존의 답변을 업데이트하는 함수 테스트 func testUpdateAnswer() { let expectation = self.expectation(description: "updateAnswer") let answerId = testAnswerId - repository.updateAnswer(answerId: answerId, content: "Updated content", images: ["UpdatedImageURL"]) { success in + let testImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + let testthumbnailImages = [UIImage(named: "updateAppIcon")!, UIImage(named: "updateAppIcon")!] + + repository.updateAnswer(answerId: answerId, content: "Updated content", images: testImages, thumbnailImages: testthumbnailImages) { success in XCTAssertTrue(success, "Answer update should succeed.") expectation.fulfill() } From 293d6272af0dd7aa9c9ac08f0d1f0ed00ff8bdcc Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 14 Jun 2024 19:20:29 +0900 Subject: [PATCH 09/12] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HappyAnding.xcodeproj/project.pbxproj | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj index 0d1b8771..1c3ba862 100644 --- a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj +++ b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj @@ -125,6 +125,14 @@ A3FF01882918581E00384211 /* LicenseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3FF01872918581E00384211 /* LicenseView.swift */; }; A3FF018A2918F8EF00384211 /* apache.txt in Resources */ = {isa = PBXBuildFile; fileRef = A3FF01892918F8EF00384211 /* apache.txt */; }; A3FF018E291ACFA500384211 /* WithdrawalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3FF018D291ACFA500384211 /* WithdrawalView.swift */; }; + F86DC3902BBC74CB00926285 /* CommunityRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86DC38F2BBC74CB00926285 /* CommunityRepository.swift */; }; + F86DC3922BBDB7E800926285 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86DC3912BBDB7E800926285 /* Post.swift */; }; + F86DC3942BBDB7F200926285 /* Answer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86DC3932BBDB7F200926285 /* Answer.swift */; }; + F86DC3962BBDBECF00926285 /* CommunityComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86DC3952BBDBECF00926285 /* CommunityComment.swift */; }; + F86DC3982BBDE17C00926285 /* CommunityRepositoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86DC3972BBDE17C00926285 /* CommunityRepositoryTest.swift */; }; + F86DC39B2BBE7A0600926285 /* PostType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86DC39A2BBE7A0600926285 /* PostType.swift */; }; + F86DC39D2BC92FCD00926285 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = F86DC39C2BC92FCD00926285 /* FirebaseStorage */; }; + F86E620A2BE35DFE00E26806 /* SearchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86E62092BE35DFE00E26806 /* SearchRepository.swift */; }; F90DEA4F29327E4D002140E2 /* NavigationStackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8764C0D6291F85DF00E1593B /* NavigationStackModel.swift */; }; F90DEA5029327E5D002140E2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87E99C7128F94EA8009B691F /* Assets.xcassets */; }; F90DEA5129327E62002140E2 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3D41EE07290A4C18008BE986 /* Launch Screen.storyboard */; }; @@ -301,6 +309,13 @@ A3FF01872918581E00384211 /* LicenseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseView.swift; sourceTree = ""; }; A3FF01892918F8EF00384211 /* apache.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = apache.txt; sourceTree = ""; }; A3FF018D291ACFA500384211 /* WithdrawalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithdrawalView.swift; sourceTree = ""; }; + F86DC38F2BBC74CB00926285 /* CommunityRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityRepository.swift; sourceTree = ""; }; + F86DC3912BBDB7E800926285 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; + F86DC3932BBDB7F200926285 /* Answer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Answer.swift; sourceTree = ""; }; + F86DC3952BBDBECF00926285 /* CommunityComment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityComment.swift; sourceTree = ""; }; + F86DC3972BBDE17C00926285 /* CommunityRepositoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityRepositoryTest.swift; sourceTree = ""; }; + F86DC39A2BBE7A0600926285 /* PostType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostType.swift; sourceTree = ""; }; + F86E62092BE35DFE00E26806 /* SearchRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRepository.swift; sourceTree = ""; }; F9131B6A2922D38D00868A0E /* Keyword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keyword.swift; sourceTree = ""; }; F9136EB5293612310034AAB2 /* ShortcutsZipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsZipView.swift; sourceTree = ""; }; F91A72C0299915C500CA135A /* MoreCaptionTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreCaptionTextView.swift; sourceTree = ""; }; @@ -336,6 +351,7 @@ F94B435D2907B19A00987819 /* FirebaseAnalytics in Frameworks */, F94B43632907B19A00987819 /* FirebaseFirestoreCombine-Community in Frameworks */, F94B435F2907B19A00987819 /* FirebaseAuth in Frameworks */, + F86DC39D2BC92FCD00926285 /* FirebaseStorage in Frameworks */, F94B43612907B19A00987819 /* FirebaseFirestore in Frameworks */, 4D6A9EFF29A36E9C00D02522 /* WrappingHStack in Frameworks */, ); @@ -489,6 +505,7 @@ 87E99C6C28F94EA6009B691F /* HappyAnding */ = { isa = PBXGroup; children = ( + F86DC38E2BBC74B600926285 /* Repository */, 87E606AD2910623C00C3DA13 /* HappyAnding.entitlements */, 3D41EE06290A458B008BE986 /* Info.plist */, 87E99CC22901454D009B691F /* Extensions */, @@ -516,6 +533,7 @@ isa = PBXGroup; children = ( 87E99C7E28F94EA8009B691F /* HappyAndingTests.swift */, + F86DC3972BBDE17C00926285 /* CommunityRepositoryTest.swift */, ); path = HappyAndingTests; sourceTree = ""; @@ -672,6 +690,10 @@ F91F09DC29AE012600E04FA0 /* ShortcutGrade.swift */, 872B5D3C2A2E0FF9008DCC57 /* CurationType.swift */, A323D3C92AEE870700DDA716 /* SuggestionForm.swift */, + F86DC3912BBDB7E800926285 /* Post.swift */, + F86DC3932BBDB7F200926285 /* Answer.swift */, + F86DC3952BBDBECF00926285 /* CommunityComment.swift */, + F86DC39A2BBE7A0600926285 /* PostType.swift */, ); path = Model; sourceTree = ""; @@ -766,6 +788,15 @@ path = SettingViews; sourceTree = ""; }; + F86DC38E2BBC74B600926285 /* Repository */ = { + isa = PBXGroup; + children = ( + F86DC38F2BBC74CB00926285 /* CommunityRepository.swift */, + F86E62092BE35DFE00E26806 /* SearchRepository.swift */, + ); + path = Repository; + sourceTree = ""; + }; F9DB8ECB293B30EC00516CE1 /* Recovered References */ = { isa = PBXGroup; children = ( @@ -798,6 +829,7 @@ F94B43622907B19A00987819 /* FirebaseFirestoreCombine-Community */, 4D6A9EFE29A36E9C00D02522 /* WrappingHStack */, 4D93D0762A73C9330042CBA8 /* FirebaseMessaging */, + F86DC39C2BC92FCD00926285 /* FirebaseStorage */, ); productName = HappyAnding; productReference = 87E99C6A28F94EA6009B691F /* HappyAnding.app */; @@ -993,6 +1025,7 @@ F96D45B72980301F000C2441 /* SubtitleTextView.swift in Sources */, A323D3CA2AEE870700DDA716 /* SuggestionForm.swift in Sources */, 4DF15D752A4ECE1F0014F854 /* ListCategoryShortcutViewModel.swift in Sources */, + F86DC3902BBC74CB00926285 /* CommunityRepository.swift in Sources */, 87E99CDB29042CCA009B691F /* Category.swift in Sources */, 876B4F6F299E3D91009672D9 /* NavigationRouter.swift in Sources */, A04ACB062903D0B2004A85A6 /* MyShortcutCardView.swift in Sources */, @@ -1000,7 +1033,9 @@ F9CAEF832914855900224B0A /* Date+String.swift in Sources */, 87E606B629114F7D00C3DA13 /* WriteNicknameView.swift in Sources */, 4D7D16072986BBD7008B3332 /* TextLiteral.swift in Sources */, + F86DC3922BBDB7E800926285 /* Post.swift in Sources */, 87E99CC128FFF2B5009B691F /* CategoryModalView.swift in Sources */, + F86DC3962BBDBECF00926285 /* CommunityComment.swift in Sources */, 87E606B829114FB200C3DA13 /* UserAuth.swift in Sources */, 8788E1A02A48408F007C3852 /* ExploreCurationViewModel.swift in Sources */, 8786B33E29ABA5A9000B46A1 /* View+Shape.swift in Sources */, @@ -1021,6 +1056,7 @@ F91A72C32999160E00CA135A /* Alerter.swift in Sources */, 87E99CAD28FFF261009B691F /* ReadShortcutView.swift in Sources */, 4D5889E82AA36A52000C4849 /* AppDelegate.swift in Sources */, + F86DC39B2BBE7A0600926285 /* PostType.swift in Sources */, A33F74AE2908D8C800B8D0D0 /* CheckBoxShortcutCell.swift in Sources */, 87E606B22910649B00C3DA13 /* SignInWithAppleView.swift in Sources */, A38F3B1F2AE62E8D0036FCAC /* SuggestionFormView.swift in Sources */, @@ -1041,8 +1077,10 @@ 87E99CC9290145B8009B691F /* ListShortcutView.swift in Sources */, F92766552A61A032009C4EC2 /* WriteShortcutModalViewModel.swift in Sources */, A3FF01862918552E00384211 /* AboutTeamView.swift in Sources */, + F86E620A2BE35DFE00E26806 /* SearchRepository.swift in Sources */, 87E99C6E28F94EA6009B691F /* HappyAndingApp.swift in Sources */, 87E99CD32901465F009B691F /* ValidationCheckTextField.swift in Sources */, + F86DC3942BBDB7F200926285 /* Answer.swift in Sources */, 87DBFB062A2127C0000CC442 /* CheckVersionView.swift in Sources */, A3FF01882918581E00384211 /* LicenseView.swift in Sources */, A3439AFB2939B0E80043E273 /* UserDefaults+Extension.swift in Sources */, @@ -1068,6 +1106,7 @@ buildActionMask = 2147483647; files = ( 87E99C7F28F94EA8009B691F /* HappyAndingTests.swift in Sources */, + F86DC3982BBDE17C00926285 /* CommunityRepositoryTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1286,7 +1325,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipProfile_Dev_20231113; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipProfile_Dev_20240317; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1329,7 +1368,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipProfile_Dev_20231113; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipProfile_Dev_20240317; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1438,7 +1477,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20231113; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20240317; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1470,7 +1509,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20231113; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20240317; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1578,6 +1617,11 @@ package = F94B435B2907B19A00987819 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = "FirebaseFirestoreCombine-Community"; }; + F86DC39C2BC92FCD00926285 /* FirebaseStorage */ = { + isa = XCSwiftPackageProductDependency; + package = F94B435B2907B19A00987819 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseStorage; + }; F94B435C2907B19A00987819 /* FirebaseAnalytics */ = { isa = XCSwiftPackageProductDependency; package = F94B435B2907B19A00987819 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; From 99fd103517e7b2510d1de93b743e35325e1fedba Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 14 Jun 2024 19:24:57 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/SearchRepository.swift | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 HappyAnding/HappyAnding/Repository/SearchRepository.swift diff --git a/HappyAnding/HappyAnding/Repository/SearchRepository.swift b/HappyAnding/HappyAnding/Repository/SearchRepository.swift new file mode 100644 index 00000000..ddc8440e --- /dev/null +++ b/HappyAnding/HappyAnding/Repository/SearchRepository.swift @@ -0,0 +1,74 @@ +// +// SearchRepository.swift +// HappyAnding +// +// Created by 임정욱 on 5/2/24. +// + +import Foundation +import FirebaseCore +import FirebaseFirestore + + +class SearchRepository { + + + private let db = Firestore.firestore() + private let postCollection: String = "Post" + private let shortcutCollection: String = "Shortcut" + + + + public func searchContentAndShortcuts(keyword: String, completion: @escaping ([[Any]]) -> Void) { + let db = Firestore.firestore() + let postsRef = db.collection(postCollection) + let shortcutsRef = db.collection(shortcutCollection) + + let group = DispatchGroup() + var postsResults = [Post]() + var shortcutsResults = [Shortcuts]() + var errors: [Error] = [] + + let searchFieldsShortcuts = ["title", "subtitle", "description"] + for field in searchFieldsShortcuts { + group.enter() + shortcutsRef.whereField(field, arrayContains: keyword).getDocuments { (snapshot, error) in + defer { group.leave() } + if let snapshot = snapshot { + shortcutsResults += snapshot.documents.compactMap { document -> Shortcuts? in + try? document.data(as: Shortcuts.self) + } + } else if let error = error { + errors.append(error) + } + } + } + + let searchFieldsPosts = ["title", "content"] + for field in searchFieldsPosts { + group.enter() + postsRef.whereField(field, arrayContains: keyword).getDocuments { (snapshot, error) in + defer { group.leave() } + if let snapshot = snapshot { + postsResults += snapshot.documents.compactMap { document -> Post? in + try? document.data(as: Post.self) + } + } else if let error = error { + errors.append(error) + } + } + } + + group.notify(queue: .main) { + if errors.isEmpty { + let combinedResults = [shortcutsResults as [Any], postsResults as [Any]] + completion(combinedResults) + } else { + completion([]) + } + } + } + + + +} From 976847690d846720586b9ee75c90b323f04664c4 Mon Sep 17 00:00:00 2001 From: Halogen <030212hgjun@gmail.com> Date: Fri, 14 Jun 2024 20:06:07 +0900 Subject: [PATCH 11/12] [Fix] #520 - Fix signing error on test target --- .../HappyAnding.xcodeproj/project.pbxproj | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj index 1c3ba862..42824282 100644 --- a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj +++ b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj @@ -1300,12 +1300,11 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = HappyAnding/HappyAnding.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HappyAnding/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HN3RL67C46; + DEVELOPMENT_TEAM = HN3RL67C46; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = HappyAnding/Info.plist; @@ -1325,7 +1324,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipProfile_Dev_20240317; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1343,12 +1341,11 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = HappyAnding/HappyAnding.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HappyAnding/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HN3RL67C46; + DEVELOPMENT_TEAM = HN3RL67C46; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = HappyAnding/Info.plist; @@ -1368,7 +1365,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipProfile_Dev_20240317; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1385,7 +1381,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7JPPWR5997; + DEVELOPMENT_TEAM = HN3RL67C46; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; MARKETING_VERSION = 1.0; @@ -1405,7 +1401,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7JPPWR5997; + DEVELOPMENT_TEAM = HN3RL67C46; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; MARKETING_VERSION = 1.0; @@ -1424,7 +1420,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7JPPWR5997; + DEVELOPMENT_TEAM = HN3RL67C46; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAndingUITests; @@ -1442,7 +1438,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7JPPWR5997; + DEVELOPMENT_TEAM = HN3RL67C46; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAndingUITests; @@ -1459,11 +1455,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HN3RL67C46; + DEVELOPMENT_TEAM = HN3RL67C46; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; @@ -1477,7 +1472,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20240317; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1491,11 +1485,10 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HN3RL67C46; + DEVELOPMENT_TEAM = HN3RL67C46; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; @@ -1509,7 +1502,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20240317; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; From e3493d55e83f2031d66f9179da918ef64fd66ad3 Mon Sep 17 00:00:00 2001 From: YJU Date: Fri, 14 Jun 2024 21:31:13 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[Chore]=20#520=20-=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HappyAnding.xcodeproj/project.pbxproj | 18 +-- .../Repository/SearchRepository.swift | 142 +++++++++--------- .../HappyAnding/Views/SearchViewModel.swift | 112 +++++++------- 3 files changed, 135 insertions(+), 137 deletions(-) diff --git a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj index e143779f..4ed19c1a 100644 --- a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj +++ b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj @@ -1361,7 +1361,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HappyAnding/Preview Content\""; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = HappyAnding/Info.plist; @@ -1403,7 +1403,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"HappyAnding/Preview Content\""; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = HappyAnding/Info.plist; @@ -1441,7 +1441,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; MARKETING_VERSION = 1.0; @@ -1463,7 +1463,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; MARKETING_VERSION = 1.0; @@ -1484,7 +1484,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAndingUITests; @@ -1504,7 +1504,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAndingUITests; @@ -1525,7 +1525,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; @@ -1539,7 +1539,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20240317; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1556,7 +1555,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = HN3RL67C46; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; @@ -1570,7 +1569,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.happyanding.HappyAnding.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = ShortcutsZipShareExtProfile_Dev_20240317; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/HappyAnding/HappyAnding/Repository/SearchRepository.swift b/HappyAnding/HappyAnding/Repository/SearchRepository.swift index ddc8440e..4432c57b 100644 --- a/HappyAnding/HappyAnding/Repository/SearchRepository.swift +++ b/HappyAnding/HappyAnding/Repository/SearchRepository.swift @@ -1,74 +1,74 @@ +//// +//// SearchRepository.swift +//// HappyAnding +//// +//// Created by 임정욱 on 5/2/24. +//// // -// SearchRepository.swift -// HappyAnding +//import Foundation +//import FirebaseCore +//import FirebaseFirestore // -// Created by 임정욱 on 5/2/24. // - -import Foundation -import FirebaseCore -import FirebaseFirestore - - -class SearchRepository { - - - private let db = Firestore.firestore() - private let postCollection: String = "Post" - private let shortcutCollection: String = "Shortcut" - - - - public func searchContentAndShortcuts(keyword: String, completion: @escaping ([[Any]]) -> Void) { - let db = Firestore.firestore() - let postsRef = db.collection(postCollection) - let shortcutsRef = db.collection(shortcutCollection) - - let group = DispatchGroup() - var postsResults = [Post]() - var shortcutsResults = [Shortcuts]() - var errors: [Error] = [] - - let searchFieldsShortcuts = ["title", "subtitle", "description"] - for field in searchFieldsShortcuts { - group.enter() - shortcutsRef.whereField(field, arrayContains: keyword).getDocuments { (snapshot, error) in - defer { group.leave() } - if let snapshot = snapshot { - shortcutsResults += snapshot.documents.compactMap { document -> Shortcuts? in - try? document.data(as: Shortcuts.self) - } - } else if let error = error { - errors.append(error) - } - } - } - - let searchFieldsPosts = ["title", "content"] - for field in searchFieldsPosts { - group.enter() - postsRef.whereField(field, arrayContains: keyword).getDocuments { (snapshot, error) in - defer { group.leave() } - if let snapshot = snapshot { - postsResults += snapshot.documents.compactMap { document -> Post? in - try? document.data(as: Post.self) - } - } else if let error = error { - errors.append(error) - } - } - } - - group.notify(queue: .main) { - if errors.isEmpty { - let combinedResults = [shortcutsResults as [Any], postsResults as [Any]] - completion(combinedResults) - } else { - completion([]) - } - } - } - - - -} +//class SearchRepository { +// +// +// private let db = Firestore.firestore() +// private let postCollection: String = "Post" +// private let shortcutCollection: String = "Shortcut" +// +// +// +// public func searchContentAndShortcuts(keyword: String, completion: @escaping ([[Any]]) -> Void) { +// let db = Firestore.firestore() +// let postsRef = db.collection(postCollection) +// let shortcutsRef = db.collection(shortcutCollection) +// +// let group = DispatchGroup() +// var postsResults = [Post]() +// var shortcutsResults = [Shortcuts]() +// var errors: [Error] = [] +// +// let searchFieldsShortcuts = ["title", "subtitle", "description"] +// for field in searchFieldsShortcuts { +// group.enter() +// shortcutsRef.whereField(field, arrayContains: keyword).getDocuments { (snapshot, error) in +// defer { group.leave() } +// if let snapshot = snapshot { +// shortcutsResults += snapshot.documents.compactMap { document -> Shortcuts? in +// try? document.data(as: Shortcuts.self) +// } +// } else if let error = error { +// errors.append(error) +// } +// } +// } +// +// let searchFieldsPosts = ["title", "content"] +// for field in searchFieldsPosts { +// group.enter() +// postsRef.whereField(field, arrayContains: keyword).getDocuments { (snapshot, error) in +// defer { group.leave() } +// if let snapshot = snapshot { +// postsResults += snapshot.documents.compactMap { document -> Post? in +// try? document.data(as: Post.self) +// } +// } else if let error = error { +// errors.append(error) +// } +// } +// } +// +// group.notify(queue: .main) { +// if errors.isEmpty { +// let combinedResults = [shortcutsResults as [Any], postsResults as [Any]] +// completion(combinedResults) +// } else { +// completion([]) +// } +// } +// } +// +// +// +//} diff --git a/HappyAnding/HappyAnding/Views/SearchViewModel.swift b/HappyAnding/HappyAnding/Views/SearchViewModel.swift index 819ca8f3..9e1dfce7 100644 --- a/HappyAnding/HappyAnding/Views/SearchViewModel.swift +++ b/HappyAnding/HappyAnding/Views/SearchViewModel.swift @@ -8,62 +8,62 @@ import SwiftUI //TODO: 레포지터리 머지 시 삭제 필요 -enum PostType: String, Codable { - case General = "General" - case Question = "Question" -} - -struct Post: Identifiable, Codable, Equatable, Hashable { - - let id : String - let type: PostType - let createdAt: String - let author: String - - var content: String - var shortcuts: [String] - var images: [String] - var likedBy: [String:Bool] - var likeCount: Int - var commentCount: Int - - init(type: PostType, content: String, author: String, shortcuts: [String] = [], images: [String] = []) { - - self.id = UUID().uuidString - self.createdAt = Date().getDate() - - self.type = type - self.content = content - self.author = author - self.shortcuts = shortcuts - self.images = images - - self.likeCount = 0 - self.commentCount = 0 - self.likedBy = [:] - - } - - init() { - self.id = UUID().uuidString - self.createdAt = Date().getDate() - - self.type = .General - self.content = "가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하" - self.author = "author" - self.shortcuts = ["shortcuts"] - self.images = ["images"] - - self.likeCount = 0 - self.commentCount = 0 - self.likedBy = [:] - } - - var dictionary: [String: Any] { - let data = (try? JSONEncoder().encode(self)) ?? Data() - return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] - } -} +//enum PostType: String, Codable { +// case General = "General" +// case Question = "Question" +//} +// +//struct Post: Identifiable, Codable, Equatable, Hashable { +// +// let id : String +// let type: PostType +// let createdAt: String +// let author: String +// +// var content: String +// var shortcuts: [String] +// var images: [String] +// var likedBy: [String:Bool] +// var likeCount: Int +// var commentCount: Int +// +// init(type: PostType, content: String, author: String, shortcuts: [String] = [], images: [String] = []) { +// +// self.id = UUID().uuidString +// self.createdAt = Date().getDate() +// +// self.type = type +// self.content = content +// self.author = author +// self.shortcuts = shortcuts +// self.images = images +// +// self.likeCount = 0 +// self.commentCount = 0 +// self.likedBy = [:] +// +// } +// +// init() { +// self.id = UUID().uuidString +// self.createdAt = Date().getDate() +// +// self.type = .General +// self.content = "가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하" +// self.author = "author" +// self.shortcuts = ["shortcuts"] +// self.images = ["images"] +// +// self.likeCount = 0 +// self.commentCount = 0 +// self.likedBy = [:] +// } +// +// var dictionary: [String: Any] { +// let data = (try? JSONEncoder().encode(self)) ?? Data() +// return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] +// } +//} final class SearchViewModel: ObservableObject { private let shortcutsZipViewModel = ShortcutsZipViewModel.share