2424import com .back .global .file .dto .UploadedFileDto ;
2525import com .back .global .file .service .FileService ;
2626import com .back .global .rq .Rq ;
27+ import java .util .ArrayList ;
28+ import java .util .List ;
29+ import java .util .NoSuchElementException ;
30+ import java .util .Optional ;
31+ import java .util .stream .Collectors ;
2732import lombok .RequiredArgsConstructor ;
2833import org .springframework .stereotype .Service ;
2934import org .springframework .transaction .annotation .Transactional ;
3035import org .springframework .transaction .support .TransactionSynchronization ;
3136import org .springframework .transaction .support .TransactionSynchronizationManager ;
3237import org .springframework .web .multipart .MultipartFile ;
3338
34- import java .util .*;
35- import java .util .function .Function ;
36- import java .util .stream .Collectors ;
37-
3839@ Service
3940@ RequiredArgsConstructor
4041public class PostService {
@@ -146,63 +147,73 @@ public PostResponseDto updatePost(Long postId, PostUpdateRequestDto reqBody, Lis
146147 if (reqBody .content () != null && !reqBody .content ().isBlank ()) {
147148 post .updateContent (reqBody .content ());
148149 }
150+
151+ List <String > addedImgUrls = List .of ();
152+ List <String > uploadedFileNames = List .of ();
153+
149154 if (images != null && !images .isEmpty ()) {
150155 // 새 이미지 업로드
151156 List <UploadedFileDto > uploaded = fileService .uploadFiles (images );
152- List <String > uploadedFileNames = uploaded .stream ().map (UploadedFileDto ::fileName ).toList ();
157+ addedImgUrls = uploaded .stream ().map (UploadedFileDto ::url ).toList ();
158+ uploadedFileNames = uploaded .stream ().map (UploadedFileDto ::fileName ).toList ();
159+ }
160+
161+ // 요청 DTO에서 "유지할 이미지 URL 목록" 꺼내기
162+ List <String > keepImageUrls = new ArrayList <>(
163+ Optional .ofNullable (reqBody .keepImageUrls ()).orElse (List .of ()));
164+ keepImageUrls .addAll (addedImgUrls );
153165
154- // 요청 DTO에서 "유지할 이미지 ID 목록" 꺼내기
155- List <Long > keepIds = Optional . ofNullable ( reqBody . keepImageIds ()). orElse ( List . of ());
166+ // 🔹 현재 게시글의 모든 이미지 가져오기
167+ List <PostImage > existingImages = new ArrayList <>( post . getImages ());
156168
157- // 현재 게시글의 이미지들을 (id -> 객체) 매핑으로 변환
158- Map <Long , PostImage > existingById = post .getImages ().stream ()
159- .collect (Collectors .toMap (PostImage ::getId , Function .identity ()));
169+ // 삭제될 이미지 (DB + S3)
170+ List <PostImage > toRemove = post .getImages ().stream ()
171+ .filter (img -> !keepImageUrls .contains (img .getUrl ()))
172+ .toList ();
160173
161- // 삭제할 이미지 찾기
162- List <PostImage > toDelete = post .getImages ().stream ()
163- .filter (img -> !keepIds .contains (img .getId ()))
174+ List <String > deleteKeysAfterCommit = toRemove .stream ()
175+ .map (PostImage ::getFileName )
164176 .toList ();
165177
166- // 최종 이미지 리스트 구성
167- List <PostImage > finalImages = new ArrayList <>();
178+ toRemove .forEach (img -> img .updatePost (null )); // 관계 해제
179+ post .getImages ().removeAll (toRemove ); // orphanRemoval 트리거
180+
181+ // 유지할 이미지 정렬
168182 int order = 0 ;
169- for (Long keepId : keepIds ) {
170- PostImage img = existingById .get (keepId );
171- if (img != null ) {
172- img .updateSortOrder (order ++);
173- finalImages .add (img );
183+ for (String url : keepImageUrls ) {
184+ // 기존 이미지인지 확인
185+ PostImage existing = existingImages .stream ()
186+ .filter (img -> img .getUrl ().equals (url ))
187+ .findFirst ()
188+ .orElse (null );
189+
190+ if (existing != null ) {
191+ existing .updateSortOrder (order ++);
192+ } else {
193+ // 새로 추가된 이미지
194+ post .getImages ().add (PostImage .builder ()
195+ .post (post )
196+ .fileName (extractFileNameFromUrl (url )) // URL에서 파일명 추출 함수 아래 참고
197+ .url (url )
198+ .sortOrder (order ++)
199+ .build ());
174200 }
175201 }
176- for (UploadedFileDto u : uploaded ) {
177- finalImages .add (PostImage .builder ()
178- .post (post )
179- .fileName (u .fileName ())
180- .url (u .url ())
181- .sortOrder (order ++)
182- .build ()
183- );
184- }
185-
186- // 삭제 예정 key 모음
187- List <String > deleteKeysAfterCommit = toDelete .stream ()
188- .map (PostImage ::getFileName )
189- .toList ();
190202
191- // DB에 반영
192- post .updateImages (finalImages );
203+ List <String > uploadedNames = new ArrayList <>(uploadedFileNames );
193204
194205 // 트랜잭션 완료 후 처리
195206 TransactionSynchronizationManager .registerSynchronization (new TransactionSynchronization () {
196207 @ Override
197208 public void afterCompletion (int status ) {
198209 if (status == STATUS_ROLLED_BACK ) {
199- uploadedFileNames .forEach (fileService ::deleteFile );
210+ uploadedNames .forEach (fileService ::deleteFile );
200211 } else if (status == STATUS_COMMITTED ) {
201212 deleteKeysAfterCommit .forEach (fileService ::deleteFile );
202213 }
203214 }
204215 });
205- }
216+
206217 if (reqBody .videoUrl () != null && !reqBody .videoUrl ().isBlank ()) {
207218 post .updateVideo (reqBody .videoUrl ());
208219 }
@@ -214,6 +225,13 @@ public void afterCompletion(int status) {
214225 return new PostResponseDto (post );
215226 }
216227
228+ private String extractFileNameFromUrl (String url ) {
229+ if (url == null ) return null ;
230+ int lastSlash = url .lastIndexOf ('/' );
231+ return (lastSlash != -1 ) ? url .substring (lastSlash + 1 ) : url ;
232+ }
233+
234+
217235 // 게시글 삭제 로직
218236 @ Transactional
219237 public void deletePost (Long postId ) {
@@ -272,6 +290,17 @@ public PostLikeResponseDto toggleLike(Long postId) {
272290 }
273291 }
274292
293+ // 사용자가 해당 게시글 여부 확인 로직
294+ @ Transactional (readOnly = true )
295+ public Boolean getLike (Long postId ) {
296+ User user = rq .getActor ();
297+
298+ Post post = postRepository .findById (postId )
299+ .orElseThrow (() -> new NoSuchElementException ("해당 게시글을 찾을 수 없습니다. ID: " + postId ));
300+
301+ return postLikeRepository .existsByPostAndUser (post , user );
302+ }
303+
275304 // 태그 추가 메서드
276305 private void addTag (List <String > tagNames , Post post ) {
277306 for (String tagName : tagNames ) {
0 commit comments