diff --git a/src/main/java/io/github/petty/community/controller/CommentController.java b/src/main/java/io/github/petty/community/controller/CommentController.java index 9ec64dd..cbfbc6e 100644 --- a/src/main/java/io/github/petty/community/controller/CommentController.java +++ b/src/main/java/io/github/petty/community/controller/CommentController.java @@ -10,9 +10,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import lombok.extern.slf4j.Slf4j; import java.util.List; +@Slf4j @RestController @RequiredArgsConstructor public class CommentController { @@ -29,21 +31,24 @@ public ResponseEntity> getComments(@PathVariable Long post public ResponseEntity addComment(@PathVariable Long postId, @RequestBody CommentRequest request, @AuthenticationPrincipal CustomUserDetails userDetails) { + String username = null; try { - String username = userDetails.getUsername(); + username = userDetails.getUsername(); Users user = usersRepository.findByUsername(username); - - System.out.println("๐Ÿ”ฅ ๋Œ“๊ธ€ ๋“ฑ๋ก ์‹œ์ž‘ - postId: " + postId + ", user: " + username); - + + log.info("๋Œ“๊ธ€ ๋“ฑ๋ก ์‹œ์ž‘ - postId: {}, user: {}", postId, username); + Long commentId = commentService.addComment(postId, request, user); - - System.out.println("โœ… ๋Œ“๊ธ€ ๋“ฑ๋ก ์™„๋ฃŒ - commentId: " + commentId); - + + log.info("๋Œ“๊ธ€ ๋“ฑ๋ก ์™„๋ฃŒ - commentId: {}", commentId); + return ResponseEntity.ok().body(commentId); - } catch (Exception e) { - System.err.println("โŒ ๋Œ“๊ธ€ ๋“ฑ๋ก ์‹คํŒจ: " + e.getMessage()); - e.printStackTrace(); - return ResponseEntity.badRequest().body("๋Œ“๊ธ€ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: " + e.getMessage()); + } catch (IllegalArgumentException e) { + log.warn("๋Œ“๊ธ€ ๋“ฑ๋ก ์‹คํŒจ - ์ž˜๋ชป๋œ ์š”์ฒญ: {}", e.getMessage()); + return ResponseEntity.badRequest().body("์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค: " + e.getMessage()); + } catch (Exception e) { + log.error("๋Œ“๊ธ€ ๋“ฑ๋ก ์‹คํŒจ", e); + return ResponseEntity.badRequest().body("๋Œ“๊ธ€ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); } } @@ -60,21 +65,20 @@ public ResponseEntity updateComment(@PathVariable Long commentId, @DeleteMapping("/api/comments/{commentId}") public ResponseEntity deleteComment(@PathVariable Long commentId, @AuthenticationPrincipal CustomUserDetails userDetails) { + String username = null; try { - String username = userDetails.getUsername(); + username = userDetails.getUsername(); Users user = usersRepository.findByUsername(username); - - System.out.println("๐Ÿ”ฅ ๋Œ“๊ธ€ ์‚ญ์ œ ์‹œ์ž‘ - commentId: " + commentId + ", user: " + username); - + + log.info("๋Œ“๊ธ€ ์‚ญ์ œ ์‹œ์ž‘ - commentId: {}, user: {}", commentId, username); commentService.deleteComment(commentId, user); - - System.out.println("โœ… ๋Œ“๊ธ€ ์‚ญ์ œ ์™„๋ฃŒ - commentId: " + commentId); - + + log.info("๋Œ“๊ธ€ ์‚ญ์ œ ์™„๋ฃŒ - commentId: {}", commentId); + return ResponseEntity.noContent().build(); } catch (Exception e) { - System.err.println("โŒ ๋Œ“๊ธ€ ์‚ญ์ œ ์‹คํŒจ: " + e.getMessage()); - e.printStackTrace(); - return ResponseEntity.badRequest().body("๋Œ“๊ธ€ ์‚ญ์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: " + e.getMessage()); + log.error("๋Œ“๊ธ€ ์‚ญ์ œ ์‹คํŒจ - commentId: {}, user: {}", commentId, username, e); + return ResponseEntity.status(500).body("๋Œ“๊ธ€ ์‚ญ์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); } } } diff --git a/src/main/java/io/github/petty/community/controller/PostController.java b/src/main/java/io/github/petty/community/controller/PostController.java index 0927799..281ea02 100644 --- a/src/main/java/io/github/petty/community/controller/PostController.java +++ b/src/main/java/io/github/petty/community/controller/PostController.java @@ -13,6 +13,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -99,7 +100,13 @@ public ResponseEntity likePost(@PathVariable Long id, // ๐Ÿ”ฅ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์ž„์‹œ ์—”๋“œํฌ์ธํŠธ (๊ด€๋ฆฌ์ž์šฉ) @PostMapping("/update-counts") - public ResponseEntity updateAllPostCounts() { + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity updateAllPostCounts(@AuthenticationPrincipal CustomUserDetails userDetails) { + // ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ํ™•์ธ + if (!userDetails.getAuthorities().stream() + .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) { + return ResponseEntity.status(403).body("๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."); + } postService.updateAllPostCounts(); return ResponseEntity.ok(Map.of("message", "๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€์˜ ๋Œ“๊ธ€ ์ˆ˜์™€ ์ข‹์•„์š” ์ˆ˜๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")); } diff --git a/src/main/java/io/github/petty/community/dto/CommentRequest.java b/src/main/java/io/github/petty/community/dto/CommentRequest.java index 8484064..43d78e9 100644 --- a/src/main/java/io/github/petty/community/dto/CommentRequest.java +++ b/src/main/java/io/github/petty/community/dto/CommentRequest.java @@ -4,7 +4,7 @@ import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.Size; +import jakarta.validation.constraints.Size; @Getter @Setter diff --git a/src/main/java/io/github/petty/community/dto/PostRequest.java b/src/main/java/io/github/petty/community/dto/PostRequest.java index 67e8318..ac2f1da 100644 --- a/src/main/java/io/github/petty/community/dto/PostRequest.java +++ b/src/main/java/io/github/petty/community/dto/PostRequest.java @@ -6,7 +6,7 @@ import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.Size; +import jakarta.validation.constraints.Size; import java.util.List; import io.github.petty.community.dto.PostImageRequest; diff --git a/src/main/java/io/github/petty/community/repository/PostRepository.java b/src/main/java/io/github/petty/community/repository/PostRepository.java index 6653585..7748416 100644 --- a/src/main/java/io/github/petty/community/repository/PostRepository.java +++ b/src/main/java/io/github/petty/community/repository/PostRepository.java @@ -5,9 +5,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -21,5 +23,32 @@ public interface PostRepository extends JpaRepository { @EntityGraph(attributePaths = {"user", "images"}) Optional findWithUserAndImagesById(Long id); + + @Query(""" + UPDATE Post p + SET p.commentCount = (SELECT COUNT(c) FROM Comment c WHERE c.post = p), + p.likeCount = (SELECT COUNT(l) FROM PostLike l WHERE l.post = p) + """) + @Modifying + @Transactional + void updateAllPostCounts(); + + // ๋˜๋Š” ๋” ๋‚˜์€ ์„ฑ๋Šฅ์„ ์œ„ํ•œ ๋„ค์ดํ‹ฐ๋ธŒ ์ฟผ๋ฆฌ + @Query(value = """ + UPDATE posts p + SET comment_count = ( + SELECT COUNT(*) + FROM comments c + WHERE c.post_id = p.id + ), + like_count = ( + SELECT COUNT(*) + FROM post_likes pl + WHERE pl.post_id = p.id + ) + """, nativeQuery = true) + @Modifying + @Transactional + void updateAllPostCountsNative(); } diff --git a/src/main/java/io/github/petty/community/service/PostImageServiceImpl.java b/src/main/java/io/github/petty/community/service/PostImageServiceImpl.java index a9c5fb5..4e0ca32 100644 --- a/src/main/java/io/github/petty/community/service/PostImageServiceImpl.java +++ b/src/main/java/io/github/petty/community/service/PostImageServiceImpl.java @@ -11,6 +11,9 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -70,48 +73,45 @@ public void updateImagesFromRequest(Post post, List imageReque // 1๏ธโƒฃ ๊ธฐ์กด ์ด๋ฏธ์ง€๋“ค ์กฐํšŒ List existingImages = postImageRepository.findByPostIdOrderByOrderingAsc(post.getId()); - + // 2๏ธโƒฃ ์‚ญ์ œํ•  ์ด๋ฏธ์ง€๋“ค ์ฒ˜๋ฆฌ - for (PostImageRequest dto : imageRequests) { - if (Boolean.TRUE.equals(dto.getIsDeleted()) && dto.getId() != null) { - // ๊ธฐ์กด ์ด๋ฏธ์ง€ ์‚ญ์ œ - existingImages.stream() - .filter(img -> img.getId().equals(dto.getId())) - .findFirst() - .ifPresent(image -> { - postImageRepository.delete(image); - supabaseUploader.delete(image.getImageUrl()); - }); - } + List imagesToDelete = imageRequests.stream() + .filter(dto -> Boolean.TRUE.equals(dto.getIsDeleted()) && dto.getId() != null) + .map(PostImageRequest::getId) + .toList(); + + if (!imagesToDelete.isEmpty()) { + List deletingImages = postImageRepository.findAllById(imagesToDelete); + postImageRepository.deleteAll(deletingImages); + deletingImages.forEach(image -> supabaseUploader.delete(image.getImageUrl())); } - + // 3๏ธโƒฃ flush๋กœ ์‚ญ์ œ ์ž‘์—… ์™„๋ฃŒ postImageRepository.flush(); - + // 4๏ธโƒฃ ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋“ค ์ถ”๊ฐ€ (id๊ฐ€ ์—†๋Š” ๊ฒƒ๋“ค) for (PostImageRequest dto : imageRequests) { if (!Boolean.TRUE.equals(dto.getIsDeleted()) && dto.getId() == null) { PostImage newImage = PostImage.builder() - .imageUrl(dto.getImageUrl()) - .ordering(dto.getOrdering() != null ? dto.getOrdering() : 0) - .post(post) - .build(); + .imageUrl(dto.getImageUrl()) + .ordering(dto.getOrdering() != null ? dto.getOrdering() : 0) + .post(post) + .build(); postImageRepository.save(newImage); } } - + // 5๏ธโƒฃ ๊ธฐ์กด ์ด๋ฏธ์ง€ ์ˆœ์„œ ์—…๋ฐ์ดํŠธ (์‚ญ์ œ๋˜์ง€ ์•Š์€ ๊ฒƒ๋“ค๋งŒ) + Map existingImagesMap = existingImages.stream() + .collect(Collectors.toMap(PostImage::getId, Function.identity())); + for (PostImageRequest dto : imageRequests) { - if (!Boolean.TRUE.equals(dto.getIsDeleted()) && dto.getId() != null) { - postImageRepository.findById(dto.getId()) - .ifPresent(image -> { - if (dto.getOrdering() != null) { - image.setOrdering(dto.getOrdering()); - } - // save() ํ˜ธ์ถœํ•˜์ง€ ์•Š์Œ - @Transactional๋กœ ์ž๋™ ์ €์žฅ - }); + if (!Boolean.TRUE.equals(dto.getIsDeleted()) && dto.getId() != null && dto.getOrdering() != null) { + PostImage image = existingImagesMap.get(dto.getId()); + if (image != null) { + image.setOrdering(dto.getOrdering()); + } } } } -} - +} \ No newline at end of file diff --git a/src/main/java/io/github/petty/community/service/PostServiceImpl.java b/src/main/java/io/github/petty/community/service/PostServiceImpl.java index dcfdef4..06b5a63 100644 --- a/src/main/java/io/github/petty/community/service/PostServiceImpl.java +++ b/src/main/java/io/github/petty/community/service/PostServiceImpl.java @@ -194,21 +194,7 @@ public PostDetailResponse findById(Long id) { @Override @Transactional public void updateAllPostCounts() { - // ๐Ÿ”ฅ ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€์˜ ๋Œ“๊ธ€ ์ˆ˜์™€ ์ข‹์•„์š” ์ˆ˜๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์—…๋ฐ์ดํŠธ - List allPosts = postRepository.findAll(); - - for (Post post : allPosts) { - // ๋Œ“๊ธ€ ์ˆ˜ ์—…๋ฐ์ดํŠธ - long commentCount = commentRepository.countByPostId(post.getId()); - post.setCommentCount((int) commentCount); - - // ์ข‹์•„์š” ์ˆ˜ ์—…๋ฐ์ดํŠธ - long likeCount = postLikeRepository.countByPost(post); - post.setLikeCount((int) likeCount); - - postRepository.save(post); - } - + postRepository.updateAllPostCountsNative(); System.out.println("โœ… ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€์˜ ๋Œ“๊ธ€ ์ˆ˜์™€ ์ข‹์•„์š” ์ˆ˜๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } } \ No newline at end of file diff --git a/src/main/resources/static/js/common/edit-qna.js b/src/main/resources/static/js/common/edit-qna.js index 89611b6..fe0f2fc 100644 --- a/src/main/resources/static/js/common/edit-qna.js +++ b/src/main/resources/static/js/common/edit-qna.js @@ -37,6 +37,34 @@ async function getCurrentUser() { return null; } +// ๐Ÿ”ฅ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ํ•จ์ˆ˜ ์ถ”๊ฐ€ +function showErrorMessage(message) { + // ๊ธฐ์กด ์•Œ๋ฆผ ์ œ๊ฑฐ + removeExistingAlerts(); + + const alertDiv = document.createElement('div'); + alertDiv.className = 'alert alert-error'; + alertDiv.innerHTML = ` + โš ๏ธ + ${message} + + `; + + document.body.insertBefore(alertDiv, document.body.firstChild); + + // 5์ดˆ ํ›„ ์ž๋™ ์ œ๊ฑฐ + setTimeout(() => { + if (alertDiv.parentElement) { + alertDiv.remove(); + } + }, 5000); +} + +function removeExistingAlerts() { + const existingAlerts = document.querySelectorAll('.alert'); + existingAlerts.forEach(alert => alert.remove()); +} + document.addEventListener("DOMContentLoaded", async () => { // ๐Ÿ” ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ๊ถŒํ•œ ์ฒดํฌ const currentUser = await getCurrentUser(); @@ -70,14 +98,40 @@ document.addEventListener("DOMContentLoaded", async () => { return; } - const payload = { - title: document.getElementById("edit-qna-title").value, - content: document.getElementById("edit-qna-content").value, - petType: getRadioValue("edit-qna-petType") || "OTHER", - postType: postType, - isResolved: getIsResolvedValue(), - images: originalImages - }; + const formData = new FormData(form); + + // name ์†์„ฑ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ (๊ถŒ์žฅ) + const title = formData.get('title')?.trim(); + const content = formData.get('content')?.trim(); + const petType = formData.get('petType'); + const isResolved = formData.has('isResolved'); + + // ๐Ÿ” ํ•„์ˆ˜ ํ•„๋“œ ๊ฒ€์ฆ + if (!title) { + showErrorMessage("์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="title"]')?.focus(); // name ์†์„ฑ ํ™œ์šฉ + return; + } + + if (!content) { + showErrorMessage("๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="content"]')?.focus(); + return; + } + + if (!petType) { + showErrorMessage("๋ฐ˜๋ ค๋™๋ฌผ ์ข…๋ฅ˜๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); + return; + } + + const payload = { + title, + content, + petType, + postType: postType, + isResolved, + images: originalImages + }; const res = await fetch(`/api/posts/${postId}`, { method: "PUT", @@ -152,10 +206,14 @@ async function fetchPostForEdit() { const res = await fetch(`/api/posts/${postId}`); const post = await res.json(); - document.getElementById("edit-qna-title").value = post.title; - document.getElementById("edit-qna-content").value = post.content; + const titleElement = document.querySelector('[name="title"]') || document.getElementById("edit-qna-title"); + const contentElement = document.querySelector('[name="content"]') || document.getElementById("edit-qna-content"); + + if (titleElement) titleElement.value = post.title; + if (contentElement) contentElement.value = post.content; - const petTypeInputs = document.querySelectorAll('input[name="edit-qna-petType"]'); + // ๐Ÿ”ฅ ์ˆ˜์ •: petType name ์†์„ฑ ํ†ต์ผ (edit-qna-petType โ†’ petType) + const petTypeInputs = document.querySelectorAll('input[name="petType"]'); petTypeInputs.forEach(input => { if (input.value === post.petType) input.checked = true; }); diff --git a/src/main/resources/static/js/common/edit-review.js b/src/main/resources/static/js/common/edit-review.js index eac8447..5a87482 100644 --- a/src/main/resources/static/js/common/edit-review.js +++ b/src/main/resources/static/js/common/edit-review.js @@ -37,6 +37,34 @@ async function getCurrentUser() { return null; } +// ๐Ÿ”ฅ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ํ•จ์ˆ˜ ์ถ”๊ฐ€ +function showErrorMessage(message) { + // ๊ธฐ์กด ์•Œ๋ฆผ ์ œ๊ฑฐ + removeExistingAlerts(); + + const alertDiv = document.createElement('div'); + alertDiv.className = 'alert alert-error'; + alertDiv.innerHTML = ` + โš ๏ธ + ${message} + + `; + + document.body.insertBefore(alertDiv, document.body.firstChild); + + // 5์ดˆ ํ›„ ์ž๋™ ์ œ๊ฑฐ + setTimeout(() => { + if (alertDiv.parentElement) { + alertDiv.remove(); + } + }, 5000); +} + +function removeExistingAlerts() { + const existingAlerts = document.querySelectorAll('.alert'); + existingAlerts.forEach(alert => alert.remove()); +} + document.addEventListener("DOMContentLoaded", async () => { // ๐Ÿ” ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ๊ถŒํ•œ ์ฒดํฌ const currentUser = await getCurrentUser(); @@ -69,12 +97,41 @@ document.addEventListener("DOMContentLoaded", async () => { location.href = "/login"; return; } + + const formData = new FormData(form); + + // name ์†์„ฑ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ (๊ถŒ์žฅ) + const title = formData.get('title')?.trim(); + const content = formData.get('content')?.trim(); + const petType = formData.get('petType'); + const petName = formData.get('petName')?.trim(); + const region = formData.get('region')?.trim(); + const isResolved = formData.has('isResolved'); + + // ๐Ÿ” ํ•„์ˆ˜ ํ•„๋“œ ๊ฒ€์ฆ + if (!title) { + showErrorMessage("์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="title"]')?.focus(); // name ์†์„ฑ ํ™œ์šฉ + return; + } + + if (!content) { + showErrorMessage("๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="content"]')?.focus(); + return; + } + + if (!petType) { + showErrorMessage("๋ฐ˜๋ ค๋™๋ฌผ ์ข…๋ฅ˜๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); + return; + } + const payload = { - title: document.getElementById("edit-review-title").value, - content: document.getElementById("edit-review-content").value, - petType: getRadioValue("edit-review-petType") || "OTHER", - petName: document.getElementById("edit-petName").value, - region: document.getElementById("edit-region").value, + title, + content, + petType, + petName, + region, postType: postType, images: originalImages }; @@ -98,6 +155,16 @@ document.addEventListener("DOMContentLoaded", async () => { } }); } + + // isResolved ํ† ๊ธ€ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ + const resolvedCheckbox = document.getElementById("isResolved"); + const resolvedLabel = document.getElementById("resolvedLabel"); + + if (resolvedCheckbox && resolvedLabel) { + resolvedCheckbox.addEventListener("change", () => { + resolvedLabel.textContent = resolvedCheckbox.checked ? "ํ•ด๊ฒฐ์™„๋ฃŒ" : "๋ฏธํ•ด๊ฒฐ"; + }); + } }); // ๐Ÿ” ๋ฆฌ๋ทฐ ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ์ž ๋ณธ์ธ์ธ์ง€ ํ™•์ธ @@ -141,16 +208,20 @@ async function fetchPostForEdit() { const res = await fetch(`/api/posts/${postId}`); const post = await res.json(); - document.getElementById("edit-review-title").value = post.title; - document.getElementById("edit-review-content").value = post.content; - document.getElementById("edit-region").value = post.region || ""; - document.getElementById("edit-petName").value = post.petName || ""; + const titleElement = document.querySelector('[name="title"]') || document.getElementById("edit-review-title"); + const contentElement = document.querySelector('[name="content"]') || document.getElementById("edit-review-content"); + const petNameElement = document.querySelector('[name="petName"]') || document.getElementById("edit-review-petName"); + const regionElement = document.querySelector('[name="region"]') || document.getElementById("edit-review-region"); - const petTypeInputs = document.querySelectorAll('input[name="edit-review-petType"]'); + if (titleElement) titleElement.value = post.title; + if (contentElement) contentElement.value = post.content; + if (petNameElement) petNameElement.value = post.petName; + if (regionElement) regionElement.value = post.region; + + // ๐Ÿ”ฅ ์ˆ˜์ •: petType name ์†์„ฑ ํ†ต์ผ (edit-qna-petType โ†’ petType) + const petTypeInputs = document.querySelectorAll('input[name="petType"]'); petTypeInputs.forEach(input => { - if (input.value === post.petType) { - input.checked = true; - } + if (input.value === post.petType) input.checked = true; }); const previewBox = document.getElementById("edit-review-imagePreview"); diff --git a/src/main/resources/static/js/common/edit-showoff.js b/src/main/resources/static/js/common/edit-showoff.js index 7b7cd15..8640f81 100644 --- a/src/main/resources/static/js/common/edit-showoff.js +++ b/src/main/resources/static/js/common/edit-showoff.js @@ -37,6 +37,34 @@ async function getCurrentUser() { return null; } +// ๐Ÿ”ฅ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ํ•จ์ˆ˜ ์ถ”๊ฐ€ +function showErrorMessage(message) { + // ๊ธฐ์กด ์•Œ๋ฆผ ์ œ๊ฑฐ + removeExistingAlerts(); + + const alertDiv = document.createElement('div'); + alertDiv.className = 'alert alert-error'; + alertDiv.innerHTML = ` + โš ๏ธ + ${message} + + `; + + document.body.insertBefore(alertDiv, document.body.firstChild); + + // 5์ดˆ ํ›„ ์ž๋™ ์ œ๊ฑฐ + setTimeout(() => { + if (alertDiv.parentElement) { + alertDiv.remove(); + } + }, 5000); +} + +function removeExistingAlerts() { + const existingAlerts = document.querySelectorAll('.alert'); + existingAlerts.forEach(alert => alert.remove()); +} + document.addEventListener("DOMContentLoaded", async () => { // ๐Ÿ” ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ๊ถŒํ•œ ์ฒดํฌ const currentUser = await getCurrentUser(); @@ -70,10 +98,36 @@ document.addEventListener("DOMContentLoaded", async () => { return; } + const formData = new FormData(form); + + // name ์†์„ฑ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ (๊ถŒ์žฅ) + const title = formData.get('title')?.trim(); + const content = formData.get('content')?.trim(); + const petType = formData.get('petType'); + const isResolved = formData.has('isResolved'); + + // ๐Ÿ” ํ•„์ˆ˜ ํ•„๋“œ ๊ฒ€์ฆ + if (!title) { + showErrorMessage("์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="title"]')?.focus(); // name ์†์„ฑ ํ™œ์šฉ + return; + } + + if (!content) { + showErrorMessage("๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="content"]')?.focus(); + return; + } + + if (!petType) { + showErrorMessage("๋ฐ˜๋ ค๋™๋ฌผ ์ข…๋ฅ˜๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); + return; + } + const payload = { - title: document.getElementById("edit-showoff-title").value, - content: document.getElementById("edit-showoff-content").value, - petType: getRadioValue("edit-showoff-petType") || "OTHER", + title, + content, + petType, postType: postType, images: originalImages }; @@ -140,14 +194,16 @@ async function fetchPostForEdit() { const res = await fetch(`/api/posts/${postId}`); const post = await res.json(); - document.getElementById("edit-showoff-title").value = post.title; - document.getElementById("edit-showoff-content").value = post.content; + const titleElement = document.querySelector('[name="title"]') || document.getElementById("edit-qna-title"); + const contentElement = document.querySelector('[name="content"]') || document.getElementById("edit-qna-content"); + + if (titleElement) titleElement.value = post.title; + if (contentElement) contentElement.value = post.content; - const petTypeInputs = document.querySelectorAll('input[name="edit-showoff-petType"]'); + // ๐Ÿ”ฅ ์ˆ˜์ •: petType name ์†์„ฑ ํ†ต์ผ (edit-qna-petType โ†’ petType) + const petTypeInputs = document.querySelectorAll('input[name="petType"]'); petTypeInputs.forEach(input => { - if (input.value === post.petType) { - input.checked = true; - } + if (input.value === post.petType) input.checked = true; }); const previewBox = document.getElementById("edit-showoff-imagePreview"); diff --git a/src/main/resources/static/js/common/form.js b/src/main/resources/static/js/common/form.js index 5fe1f55..7d6204e 100644 --- a/src/main/resources/static/js/common/form.js +++ b/src/main/resources/static/js/common/form.js @@ -185,48 +185,45 @@ async function handleFormSubmit(e) { return; } - // ํŽ˜์ด์ง€๋ณ„๋กœ ์š”์†Œ ์ฐพ๊ธฐ - const titleElement = document.getElementById('title') || - document.getElementById('review-title') || - document.getElementById('showoff-title'); - - const contentElement = document.getElementById('content') || - document.getElementById('review-content') || - document.getElementById('showoff-content'); - - const petNameElement = document.getElementById('petName'); - const regionElement = document.getElementById('region'); - - // ๐Ÿ” ํ•„์ˆ˜ ํ•„๋“œ ๊ฒ€์ฆ - if (!titleElement?.value?.trim()) { - showErrorMessage("์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); - titleElement?.focus(); - return; - } + const form = e.target; + const formData = new FormData(form); + + // ๐Ÿ”ฅ ๋ฐฉ๋ฒ• 1: FormData์—์„œ ์ง์ ‘ ์ถ”์ถœ (name ์†์„ฑ ํ™œ์šฉ) + const title = formData.get('title')?.trim(); + const content = formData.get('content')?.trim(); + const petType = formData.get('petType'); + const petName = formData.get('petName')?.trim(); + const region = formData.get('region')?.trim(); + const isResolved = formData.has('isResolved'); // ์ฒดํฌ๋ฐ•์Šค๋Š” has๋กœ ํ™•์ธ + + // ๐Ÿ” ํ•„์ˆ˜ ํ•„๋“œ ๊ฒ€์ฆ + if (!title) { + showErrorMessage("์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="title"]')?.focus(); + return; + } - if (!contentElement?.value?.trim()) { - showErrorMessage("๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); - contentElement?.focus(); - return; - } + if (!content) { + showErrorMessage("๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + form.querySelector('[name="content"]')?.focus(); + return; + } - // ๐Ÿ” ๋ฐ˜๋ ค๋™๋ฌผ ์ข…๋ฅ˜ ๊ฒ€์ฆ - const petType = getRadioValue('petType') || getRadioValue('review-petType') || getRadioValue('showoff-petType'); - if (!petType) { - showErrorMessage("๋ฐ˜๋ ค๋™๋ฌผ ์ข…๋ฅ˜๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); - return; - } + if (!petType) { + showErrorMessage("๋ฐ˜๋ ค๋™๋ฌผ ์ข…๋ฅ˜๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); + return; + } - const postData = { - title: titleElement.value.trim(), - content: contentElement.value.trim(), - petType: petType, - petName: petNameElement?.value?.trim() || null, - region: regionElement?.value?.trim() || null, - postType: detectPostType(), - isResolved: false, - images: uploadedImages - }; + const postData = { + title, + content, + petType, + petName: petName || null, + region: region || null, + postType: detectPostType(), + isResolved: isResolved, + images: uploadedImages + }; try { const res = await fetch('/api/posts', { diff --git a/src/main/resources/templates/edit-qna.html b/src/main/resources/templates/edit-qna.html index b630a8f..d778e39 100644 --- a/src/main/resources/templates/edit-qna.html +++ b/src/main/resources/templates/edit-qna.html @@ -22,38 +22,38 @@

์งˆ๋ฌธ ์ˆ˜์ •ํ•˜๊ธฐ

- +
- +
- +
- +
- +
- +
- +
- +
@@ -63,7 +63,7 @@

์งˆ๋ฌธ ์ˆ˜์ •ํ•˜๊ธฐ

๋ฏธํ•ด๊ฒฐ @@ -72,7 +72,7 @@

์งˆ๋ฌธ ์ˆ˜์ •ํ•˜๊ธฐ

- +
@@ -82,7 +82,7 @@

์งˆ๋ฌธ ์ˆ˜์ •ํ•˜๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ์—ฌ๊ธฐ์— ๋“œ๋ž˜๊ทธํ•˜๊ฑฐ๋‚˜
ํŒŒ์ผ ์„ ํƒํ•˜๊ธฐ
PNG, JPG, GIF ์ตœ๋Œ€ 5MB (์ตœ๋Œ€ 5์žฅ)
- +
diff --git a/src/main/resources/templates/edit-review.html b/src/main/resources/templates/edit-review.html index 8173696..eeb8653 100644 --- a/src/main/resources/templates/edit-review.html +++ b/src/main/resources/templates/edit-review.html @@ -22,48 +22,48 @@

ํ›„๊ธฐ ์ˆ˜์ •ํ•˜๊ธฐ

- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
@@ -71,7 +71,7 @@

ํ›„๊ธฐ ์ˆ˜์ •ํ•˜๊ธฐ

- +
@@ -81,7 +81,7 @@

ํ›„๊ธฐ ์ˆ˜์ •ํ•˜๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ์—ฌ๊ธฐ์— ๋“œ๋ž˜๊ทธํ•˜๊ฑฐ๋‚˜
ํŒŒ์ผ ์„ ํƒํ•˜๊ธฐ
PNG, JPG, GIF ์ตœ๋Œ€ 5MB (์ตœ๋Œ€ 5์žฅ)
- +
diff --git a/src/main/resources/templates/edit-showoff.html b/src/main/resources/templates/edit-showoff.html index 9ae2dce..d36533f 100644 --- a/src/main/resources/templates/edit-showoff.html +++ b/src/main/resources/templates/edit-showoff.html @@ -22,38 +22,38 @@

์ž๋ž‘๊ธ€ ์ˆ˜์ •ํ•˜๊ธฐ

- +
- +
- +
- +
- +
- +
- +
- +
@@ -61,7 +61,7 @@

์ž๋ž‘๊ธ€ ์ˆ˜์ •ํ•˜๊ธฐ

- +
@@ -71,7 +71,7 @@

์ž๋ž‘๊ธ€ ์ˆ˜์ •ํ•˜๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ์—ฌ๊ธฐ์— ๋“œ๋ž˜๊ทธํ•˜๊ฑฐ๋‚˜
ํŒŒ์ผ ์„ ํƒํ•˜๊ธฐ
PNG, JPG, GIF ์ตœ๋Œ€ 5MB (์ตœ๋Œ€ 5์žฅ)
- +
diff --git a/src/main/resources/templates/post-qna-form.html b/src/main/resources/templates/post-qna-form.html index 39ac520..542bd80 100644 --- a/src/main/resources/templates/post-qna-form.html +++ b/src/main/resources/templates/post-qna-form.html @@ -22,7 +22,7 @@

์ƒˆ ์งˆ๋ฌธ ์ž‘์„ฑํ•˜๊ธฐ

- +
@@ -61,7 +61,7 @@

์ƒˆ ์งˆ๋ฌธ ์ž‘์„ฑํ•˜๊ธฐ

- +
@@ -71,7 +71,7 @@

์ƒˆ ์งˆ๋ฌธ ์ž‘์„ฑํ•˜๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ์—ฌ๊ธฐ์— ๋“œ๋ž˜๊ทธํ•˜๊ฑฐ๋‚˜
ํŒŒ์ผ ์„ ํƒํ•˜๊ธฐ
PNG, JPG, GIF ์ตœ๋Œ€ 5MB (์ตœ๋Œ€ 5์žฅ)
- +
diff --git a/src/main/resources/templates/post-review-form.html b/src/main/resources/templates/post-review-form.html index a08d3e5..5ea7c42 100644 --- a/src/main/resources/templates/post-review-form.html +++ b/src/main/resources/templates/post-review-form.html @@ -22,48 +22,48 @@

์—ฌํ–‰์ด ์ฆ๊ฑฐ์›Œ์š”! ํ›„๊ธฐ ์ž‘์„ฑํ•˜๊ธฐ

- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
@@ -71,7 +71,7 @@

์—ฌํ–‰์ด ์ฆ๊ฑฐ์›Œ์š”! ํ›„๊ธฐ ์ž‘์„ฑํ•˜๊ธฐ

- +
@@ -81,7 +81,7 @@

์—ฌํ–‰์ด ์ฆ๊ฑฐ์›Œ์š”! ํ›„๊ธฐ ์ž‘์„ฑํ•˜๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ์—ฌ๊ธฐ์— ๋“œ๋ž˜๊ทธํ•˜๊ฑฐ๋‚˜
ํŒŒ์ผ ์„ ํƒํ•˜๊ธฐ
PNG, JPG, GIF ์ตœ๋Œ€ 5MB (์ตœ๋Œ€ 5์žฅ)
- +
diff --git a/src/main/resources/templates/post-showoff-form.html b/src/main/resources/templates/post-showoff-form.html index c5fa4b0..667f86c 100644 --- a/src/main/resources/templates/post-showoff-form.html +++ b/src/main/resources/templates/post-showoff-form.html @@ -22,38 +22,38 @@

์šฐ๋ฆฌ ์•„์ด ์ž๋ž‘ ์ž‘์„ฑํ•˜๊ธฐ

- +
- +
- +
- +
- +
- +
- +
- +
@@ -61,7 +61,7 @@

์šฐ๋ฆฌ ์•„์ด ์ž๋ž‘ ์ž‘์„ฑํ•˜๊ธฐ

- +
@@ -71,7 +71,7 @@

์šฐ๋ฆฌ ์•„์ด ์ž๋ž‘ ์ž‘์„ฑํ•˜๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ์—ฌ๊ธฐ์— ๋“œ๋ž˜๊ทธํ•˜๊ฑฐ๋‚˜
ํŒŒ์ผ ์„ ํƒํ•˜๊ธฐ
PNG, JPG, GIF ์ตœ๋Œ€ 5MB (์ตœ๋Œ€ 5์žฅ)
- +