From 1ebed94004eb0c950a15162fa2e46938ae110338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9C=A4=EC=9E=AC?= Date: Wed, 10 Dec 2025 01:03:42 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B3=A0=EA=B0=9D=EC=84=BC=ED=84=B0=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=ED=8C=90,=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1,=20=EB=8B=B5=EB=B3=80,=20=EB=8C=93=EA=B8=80?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WORK_OUT/.project | 11 + .../main/java/controller/InquiryServlet.java | 686 +++++++++++++++++ WORK_OUT/src/main/java/dao/InquiryDAO.java | 696 ++++++++++++++++++ WORK_OUT/src/main/java/dto/InquiryDTO.java | 298 ++++++++ WORK_OUT/src/main/webapp/boardDetail.jsp | 191 +++++ WORK_OUT/src/main/webapp/boardList.jsp | 118 +++ WORK_OUT/src/main/webapp/boardWrite.jsp | 54 ++ WORK_OUT/src/main/webapp/css/boardstyle.css | 531 +++++++++++++ WORK_OUT/src/main/webapp/customerService.jsp | 83 +-- 9 files changed, 2589 insertions(+), 79 deletions(-) create mode 100644 WORK_OUT/src/main/java/controller/InquiryServlet.java create mode 100644 WORK_OUT/src/main/java/dao/InquiryDAO.java create mode 100644 WORK_OUT/src/main/java/dto/InquiryDTO.java create mode 100644 WORK_OUT/src/main/webapp/boardDetail.jsp create mode 100644 WORK_OUT/src/main/webapp/boardList.jsp create mode 100644 WORK_OUT/src/main/webapp/boardWrite.jsp create mode 100644 WORK_OUT/src/main/webapp/css/boardstyle.css diff --git a/WORK_OUT/.project b/WORK_OUT/.project index 439a958..59cafc5 100644 --- a/WORK_OUT/.project +++ b/WORK_OUT/.project @@ -28,4 +28,15 @@ org.eclipse.jdt.core.javanature org.eclipse.wst.jsdt.core.jsNature + + + 1765293809622 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/WORK_OUT/src/main/java/controller/InquiryServlet.java b/WORK_OUT/src/main/java/controller/InquiryServlet.java new file mode 100644 index 0000000..716bd73 --- /dev/null +++ b/WORK_OUT/src/main/java/controller/InquiryServlet.java @@ -0,0 +1,686 @@ +package controller; + +import dao.InquiryDAO; +import dto.InquiryDTO; +import dto.UserDTO; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import java.io.IOException; +import java.util.List; + +/** + * 고객센터 문의 게시판 요청 처리 Servlet + */ +@WebServlet("/inquiry") +public class InquiryServlet extends HttpServlet { + private InquiryDAO inquiryDAO; + + @Override + public void init() throws ServletException { + inquiryDAO = InquiryDAO.getInstance(); + } + + /** + * GET: 게시글 목록, 상세보기, 작성 폼 등 + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String action = request.getParameter("action"); + if (action == null) { + action = "list"; + } + + try { + switch (action) { + case "list": + handleList(request, response); + break; + case "detail": + handleDetail(request, response); + break; + case "write": + handleWriteForm(request, response); + break; + case "edit": + handleEditForm(request, response); + break; + case "delete": + handleDelete(request, response); + break; + default: + handleList(request, response); + break; + } + } catch (Exception e) { + e.printStackTrace(); + request.setAttribute("error", "요청 처리 중 오류가 발생했습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + } + } + + /** + * POST: 게시글 작성, 수정, 답변 등 + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 요청 인코딩 설정 + request.setCharacterEncoding("UTF-8"); + + String action = request.getParameter("action"); + if (action == null) { + action = "write"; + } + + try { + switch (action) { + case "write": + handleWrite(request, response); + break; + case "edit": + handleEdit(request, response); + break; + case "reply": + handleReply(request, response); + break; + case "deleteReply": + handleDeleteReply(request, response); + break; + case "comment": + handleComment(request, response); + break; + case "deleteComment": + handleDeleteComment(request, response); + break; + case "inquiryComment": + handleInquiryComment(request, response); + break; + case "deleteInquiryComment": + handleDeleteInquiryComment(request, response); + break; + default: + response.sendRedirect(request.getContextPath() + "/inquiry?action=list"); + break; + } + } catch (Exception e) { + e.printStackTrace(); + request.setAttribute("error", "요청 처리 중 오류가 발생했습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + } + } + + /** + * 게시글 목록 조회 + */ + private void handleList(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 페이징 파라미터 + int page = 1; + try { + String pageParam = request.getParameter("page"); + if (pageParam != null && !pageParam.isEmpty()) { + page = Integer.parseInt(pageParam); + } + } catch (NumberFormatException e) { + page = 1; + } + + int pageSize = 10; + + // 검색 파라미터 + String searchType = request.getParameter("searchType"); + String searchKeyword = request.getParameter("searchKeyword"); + if (searchKeyword != null) { + searchKeyword = searchKeyword.trim(); + } + + // 게시글 목록 조회 + List inquiries = inquiryDAO.findAll(page, pageSize, searchType, searchKeyword); + int totalCount = inquiryDAO.getTotalCount(searchType, searchKeyword); + int totalPages = (int) Math.ceil((double) totalCount / pageSize); + + // 요청 속성 설정 + request.setAttribute("inquiries", inquiries); + request.setAttribute("currentPage", page); + request.setAttribute("totalPages", totalPages); + request.setAttribute("totalCount", totalCount); + request.setAttribute("pageSize", pageSize); + request.setAttribute("searchType", searchType); + request.setAttribute("searchKeyword", searchKeyword); + + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + } + + /** + * 게시글 상세보기 + */ + private void handleDetail(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String id = request.getParameter("id"); + if (id == null || id.isEmpty()) { + request.setAttribute("error", "게시글 ID가 필요합니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + InquiryDTO inquiry = inquiryDAO.findById(id); + if (inquiry == null) { + request.setAttribute("error", "게시글을 찾을 수 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + request.setAttribute("inquiry", inquiry); + request.getRequestDispatcher("/boardDetail.jsp").forward(request, response); + } + + /** + * 게시글 작성 폼 + */ + private void handleWriteForm(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + if (session == null || session.getAttribute("user") == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + request.getRequestDispatcher("/boardWrite.jsp").forward(request, response); + } + + /** + * 게시글 수정 폼 + */ + private void handleEditForm(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + String id = request.getParameter("id"); + if (id == null || id.isEmpty()) { + request.setAttribute("error", "게시글 ID가 필요합니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + InquiryDTO inquiry = inquiryDAO.findByIdWithoutIncrement(id); + if (inquiry == null) { + request.setAttribute("error", "게시글을 찾을 수 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + // 작성자 또는 관리자만 수정 가능 + if (!inquiry.getAuthor().equals(user.getUsername()) && !user.isAdmin()) { + request.setAttribute("error", "수정 권한이 없습니다."); + request.getRequestDispatcher("/boardDetail.jsp?id=" + id).forward(request, response); + return; + } + + request.setAttribute("inquiry", inquiry); + request.getRequestDispatcher("/boardWrite.jsp").forward(request, response); + } + + /** + * 게시글 작성 처리 + */ + private void handleWrite(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + String title = request.getParameter("title"); + String content = request.getParameter("content"); + String id = request.getParameter("id"); // 수정 모드인 경우 + + // 유효성 검사 + if (title == null || title.trim().isEmpty() || + content == null || content.trim().isEmpty()) { + request.setAttribute("error", "제목과 내용을 입력해주세요."); + request.getRequestDispatcher("/boardWrite.jsp").forward(request, response); + return; + } + + if (id != null && !id.isEmpty()) { + // 수정 모드 + InquiryDTO inquiry = inquiryDAO.findByIdWithoutIncrement(id); + if (inquiry == null) { + request.setAttribute("error", "게시글을 찾을 수 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + // 작성자 또는 관리자만 수정 가능 + if (!inquiry.getAuthor().equals(user.getUsername()) && !user.isAdmin()) { + request.setAttribute("error", "수정 권한이 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + inquiry.setTitle(title.trim()); + inquiry.setContent(content.trim()); + + if (inquiryDAO.update(inquiry)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + id); + } else { + request.setAttribute("error", "게시글 수정에 실패했습니다."); + request.setAttribute("inquiry", inquiry); + request.getRequestDispatcher("/boardWrite.jsp").forward(request, response); + } + } else { + // 작성 모드 + InquiryDTO inquiry = new InquiryDTO(); + inquiry.setTitle(title.trim()); + inquiry.setContent(content.trim()); + inquiry.setAuthor(user.getUsername()); + inquiry.setAuthorName(user.getName()); + + if (inquiryDAO.create(inquiry)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=list"); + } else { + request.setAttribute("error", "게시글 작성에 실패했습니다."); + request.getRequestDispatcher("/boardWrite.jsp").forward(request, response); + } + } + } + + /** + * 게시글 수정 처리 (POST) + */ + private void handleEdit(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + handleWrite(request, response); // handleWrite에서 수정 모드 처리 + } + + /** + * 게시글 삭제 + */ + private void handleDelete(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + String id = request.getParameter("id"); + if (id == null || id.isEmpty()) { + request.setAttribute("error", "게시글 ID가 필요합니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + InquiryDTO inquiry = inquiryDAO.findByIdWithoutIncrement(id); + if (inquiry == null) { + request.setAttribute("error", "게시글을 찾을 수 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + // 작성자 또는 관리자만 삭제 가능 + if (!inquiry.getAuthor().equals(user.getUsername()) && !user.isAdmin()) { + request.setAttribute("error", "삭제 권한이 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + if (inquiryDAO.delete(id)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=list"); + } else { + request.setAttribute("error", "게시글 삭제에 실패했습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + } + } + + /** + * 답변 작성 + */ + private void handleReply(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + // 관리자만 답변 가능 + if (!user.isAdmin()) { + request.setAttribute("error", "답변 권한이 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + String inquiryId = request.getParameter("inquiryId"); + String content = request.getParameter("content"); + + if (inquiryId == null || inquiryId.isEmpty() || + content == null || content.trim().isEmpty()) { + request.setAttribute("error", "답변 내용을 입력해주세요."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + return; + } + + InquiryDTO.ReplyDTO reply = new InquiryDTO.ReplyDTO(); + reply.setContent(content.trim()); + reply.setAuthor(user.getUsername()); + reply.setAuthorName(user.getName()); + + if (inquiryDAO.addReply(inquiryId, reply)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } else { + request.setAttribute("error", "답변 작성에 실패했습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } + } + + /** + * 답변 삭제 + */ + private void handleDeleteReply(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + // 관리자만 답변 삭제 가능 + if (!user.isAdmin()) { + request.setAttribute("error", "답변 삭제 권한이 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + String inquiryId = request.getParameter("inquiryId"); + String replyId = request.getParameter("replyId"); + + if (inquiryId == null || inquiryId.isEmpty() || + replyId == null || replyId.isEmpty()) { + request.setAttribute("error", "필수 파라미터가 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + if (inquiryDAO.deleteReply(inquiryId, replyId)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } else { + request.setAttribute("error", "답변 삭제에 실패했습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } + } + + /** + * 댓글 작성 (고객 또는 관리자 모두 가능) + */ + private void handleComment(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + String inquiryId = request.getParameter("inquiryId"); + String replyId = request.getParameter("replyId"); + String content = request.getParameter("content"); + + if (inquiryId == null || inquiryId.isEmpty() || + replyId == null || replyId.isEmpty() || + content == null || content.trim().isEmpty()) { + request.setAttribute("error", "댓글 내용을 입력해주세요."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + return; + } + + // 답변이 존재하는지 확인 + InquiryDTO inquiry = inquiryDAO.findByIdWithoutIncrement(inquiryId); + if (inquiry == null || inquiry.getReplies() == null) { + request.setAttribute("error", "게시글 또는 답변을 찾을 수 없습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=list"); + return; + } + + // 해당 답변이 존재하는지 확인 + boolean replyExists = false; + for (InquiryDTO.ReplyDTO reply : inquiry.getReplies()) { + if (reply.getId().equals(replyId)) { + replyExists = true; + break; + } + } + + if (!replyExists) { + request.setAttribute("error", "답변을 찾을 수 없습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + return; + } + + InquiryDTO.CommentDTO comment = new InquiryDTO.CommentDTO(); + comment.setContent(content.trim()); + comment.setAuthor(user.getUsername()); + comment.setAuthorName(user.getName()); + + if (inquiryDAO.addComment(inquiryId, replyId, comment)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } else { + request.setAttribute("error", "댓글 작성에 실패했습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } + } + + /** + * 댓글 삭제 (작성자 또는 관리자만 가능) + */ + private void handleDeleteComment(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + String inquiryId = request.getParameter("inquiryId"); + String replyId = request.getParameter("replyId"); + String commentId = request.getParameter("commentId"); + + if (inquiryId == null || inquiryId.isEmpty() || + replyId == null || replyId.isEmpty() || + commentId == null || commentId.isEmpty()) { + request.setAttribute("error", "필수 파라미터가 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + // 댓글 작성자 확인 + InquiryDTO inquiry = inquiryDAO.findByIdWithoutIncrement(inquiryId); + if (inquiry == null || inquiry.getReplies() == null) { + request.setAttribute("error", "게시글을 찾을 수 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + // 해당 댓글 찾기 및 권한 확인 + boolean hasPermission = false; + for (InquiryDTO.ReplyDTO reply : inquiry.getReplies()) { + if (reply.getId().equals(replyId) && reply.getComments() != null) { + for (InquiryDTO.CommentDTO comment : reply.getComments()) { + if (comment.getId().equals(commentId)) { + // 작성자 또는 관리자만 삭제 가능 + if (comment.getAuthor().equals(user.getUsername()) || user.isAdmin()) { + hasPermission = true; + } + break; + } + } + break; + } + } + + if (!hasPermission) { + request.setAttribute("error", "댓글 삭제 권한이 없습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + return; + } + + if (inquiryDAO.deleteComment(inquiryId, replyId, commentId)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } else { + request.setAttribute("error", "댓글 삭제에 실패했습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } + } + + /** + * 게시글 댓글 작성 (고객 또는 관리자 모두 가능) + */ + private void handleInquiryComment(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + String inquiryId = request.getParameter("inquiryId"); + String content = request.getParameter("content"); + + if (inquiryId == null || inquiryId.isEmpty() || + content == null || content.trim().isEmpty()) { + request.setAttribute("error", "댓글 내용을 입력해주세요."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + return; + } + + // 게시글이 존재하는지 확인 + InquiryDTO inquiry = inquiryDAO.findByIdWithoutIncrement(inquiryId); + if (inquiry == null) { + request.setAttribute("error", "게시글을 찾을 수 없습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=list"); + return; + } + + InquiryDTO.CommentDTO comment = new InquiryDTO.CommentDTO(); + comment.setContent(content.trim()); + comment.setAuthor(user.getUsername()); + comment.setAuthorName(user.getName()); + + if (inquiryDAO.addInquiryComment(inquiryId, comment)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } else { + request.setAttribute("error", "댓글 작성에 실패했습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } + } + + /** + * 게시글 댓글 삭제 (작성자 또는 관리자만 가능) + */ + private void handleDeleteInquiryComment(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + // 로그인 확인 + HttpSession session = request.getSession(false); + UserDTO user = (UserDTO) session.getAttribute("user"); + if (user == null) { + request.setAttribute("error", "로그인이 필요합니다."); + request.getRequestDispatcher("/login.jsp").forward(request, response); + return; + } + + String inquiryId = request.getParameter("inquiryId"); + String commentId = request.getParameter("commentId"); + + if (inquiryId == null || inquiryId.isEmpty() || + commentId == null || commentId.isEmpty()) { + request.setAttribute("error", "필수 파라미터가 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + // 댓글 작성자 확인 + InquiryDTO inquiry = inquiryDAO.findByIdWithoutIncrement(inquiryId); + if (inquiry == null || inquiry.getComments() == null) { + request.setAttribute("error", "게시글 또는 댓글을 찾을 수 없습니다."); + request.getRequestDispatcher("/boardList.jsp").forward(request, response); + return; + } + + // 해당 댓글 찾기 및 권한 확인 + boolean hasPermission = false; + for (InquiryDTO.CommentDTO comment : inquiry.getComments()) { + if (comment.getId().equals(commentId)) { + // 작성자 또는 관리자만 삭제 가능 + if (comment.getAuthor().equals(user.getUsername()) || user.isAdmin()) { + hasPermission = true; + } + break; + } + } + + if (!hasPermission) { + request.setAttribute("error", "댓글 삭제 권한이 없습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + return; + } + + if (inquiryDAO.deleteInquiryComment(inquiryId, commentId)) { + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } else { + request.setAttribute("error", "댓글 삭제에 실패했습니다."); + response.sendRedirect(request.getContextPath() + "/inquiry?action=detail&id=" + inquiryId); + } + } +} + diff --git a/WORK_OUT/src/main/java/dao/InquiryDAO.java b/WORK_OUT/src/main/java/dao/InquiryDAO.java new file mode 100644 index 0000000..3007735 --- /dev/null +++ b/WORK_OUT/src/main/java/dao/InquiryDAO.java @@ -0,0 +1,696 @@ +package dao; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import com.mongodb.client.model.Sorts; +import dto.InquiryDTO; +import mongoutil.MongoConn; +import org.bson.Document; +import org.bson.types.ObjectId; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 고객센터 문의 게시글을 관리하는 DAO 클래스 (MongoDB 기반) + */ +public class InquiryDAO { + // 싱글톤 패턴 + private static InquiryDAO instance = new InquiryDAO(); + + private MongoCollection inquiryCollection; + + private InquiryDAO() { + MongoDatabase database = MongoConn.getDatabase(); + inquiryCollection = database.getCollection("customer_board"); + } + + public static InquiryDAO getInstance() { + return instance; + } + + /** + * InquiryDTO를 MongoDB Document로 변환 + */ + private Document inquiryToDocument(InquiryDTO inquiry) { + Document doc = new Document(); + if (inquiry.getId() != null) { + doc.append("_id", new ObjectId(inquiry.getId())); + } + doc.append("title", inquiry.getTitle()) + .append("content", inquiry.getContent()) + .append("author", inquiry.getAuthor()) + .append("authorName", inquiry.getAuthorName()) + .append("viewCount", inquiry.getViewCount()) + .append("status", inquiry.getStatus() != null ? inquiry.getStatus() : "답변대기") + .append("createdAt", Date.from(inquiry.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + + // 게시글 댓글 목록 추가 + if (inquiry.getComments() != null && !inquiry.getComments().isEmpty()) { + List commentDocs = new ArrayList<>(); + for (InquiryDTO.CommentDTO comment : inquiry.getComments()) { + Document commentDoc = new Document(); + if (comment.getId() != null) { + commentDoc.append("_id", new ObjectId(comment.getId())); + } + commentDoc.append("content", comment.getContent()) + .append("author", comment.getAuthor()) + .append("authorName", comment.getAuthorName()) + .append("createdAt", Date.from(comment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + commentDocs.add(commentDoc); + } + doc.append("comments", commentDocs); + } + + // 답변 목록 추가 + if (inquiry.getReplies() != null && !inquiry.getReplies().isEmpty()) { + List replyDocs = new ArrayList<>(); + for (InquiryDTO.ReplyDTO reply : inquiry.getReplies()) { + Document replyDoc = new Document(); + if (reply.getId() != null) { + replyDoc.append("_id", new ObjectId(reply.getId())); + } + replyDoc.append("content", reply.getContent()) + .append("author", reply.getAuthor()) + .append("authorName", reply.getAuthorName()) + .append("createdAt", Date.from(reply.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + + // 댓글 목록 추가 + if (reply.getComments() != null && !reply.getComments().isEmpty()) { + List commentDocs = new ArrayList<>(); + for (InquiryDTO.CommentDTO comment : reply.getComments()) { + Document commentDoc = new Document(); + if (comment.getId() != null) { + commentDoc.append("_id", new ObjectId(comment.getId())); + } + commentDoc.append("content", comment.getContent()) + .append("author", comment.getAuthor()) + .append("authorName", comment.getAuthorName()) + .append("createdAt", Date.from(comment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + commentDocs.add(commentDoc); + } + replyDoc.append("comments", commentDocs); + } + + replyDocs.add(replyDoc); + } + doc.append("replies", replyDocs); + } + + return doc; + } + + /** + * MongoDB Document를 InquiryDTO로 변환 + */ + private InquiryDTO documentToInquiry(Document doc) { + if (doc == null) return null; + + InquiryDTO inquiry = new InquiryDTO(); + inquiry.setId(doc.getObjectId("_id").toString()); + inquiry.setTitle(doc.getString("title")); + inquiry.setContent(doc.getString("content")); + inquiry.setAuthor(doc.getString("author")); + inquiry.setAuthorName(doc.getString("authorName")); + inquiry.setViewCount(doc.getInteger("viewCount", 0)); + inquiry.setStatus(doc.getString("status")); + + // 작성일시 변환 + if (doc.containsKey("createdAt")) { + Date createdAt = doc.getDate("createdAt"); + inquiry.setCreatedAt(LocalDateTime.ofInstant(createdAt.toInstant(), ZoneId.systemDefault())); + } + + // 게시글 댓글 목록 변환 + if (doc.containsKey("comments")) { + @SuppressWarnings("unchecked") + List commentDocs = (List) doc.get("comments"); + List comments = new ArrayList<>(); + if (commentDocs != null) { + for (Document commentDoc : commentDocs) { + InquiryDTO.CommentDTO comment = new InquiryDTO.CommentDTO(); + if (commentDoc.containsKey("_id")) { + comment.setId(commentDoc.getObjectId("_id").toString()); + } + comment.setContent(commentDoc.getString("content")); + comment.setAuthor(commentDoc.getString("author")); + comment.setAuthorName(commentDoc.getString("authorName")); + if (commentDoc.containsKey("createdAt")) { + Date commentCreatedAt = commentDoc.getDate("createdAt"); + comment.setCreatedAt(LocalDateTime.ofInstant(commentCreatedAt.toInstant(), ZoneId.systemDefault())); + } + comments.add(comment); + } + } + inquiry.setComments(comments); + } + + // 답변 목록 변환 + if (doc.containsKey("replies")) { + @SuppressWarnings("unchecked") + List replyDocs = (List) doc.get("replies"); + List replies = new ArrayList<>(); + if (replyDocs != null) { + for (Document replyDoc : replyDocs) { + InquiryDTO.ReplyDTO reply = new InquiryDTO.ReplyDTO(); + if (replyDoc.containsKey("_id")) { + reply.setId(replyDoc.getObjectId("_id").toString()); + } + reply.setContent(replyDoc.getString("content")); + reply.setAuthor(replyDoc.getString("author")); + reply.setAuthorName(replyDoc.getString("authorName")); + if (replyDoc.containsKey("createdAt")) { + Date replyCreatedAt = replyDoc.getDate("createdAt"); + reply.setCreatedAt(LocalDateTime.ofInstant(replyCreatedAt.toInstant(), ZoneId.systemDefault())); + } + + // 댓글 목록 변환 + if (replyDoc.containsKey("comments")) { + @SuppressWarnings("unchecked") + List commentDocs = (List) replyDoc.get("comments"); + List comments = new ArrayList<>(); + if (commentDocs != null) { + for (Document commentDoc : commentDocs) { + InquiryDTO.CommentDTO comment = new InquiryDTO.CommentDTO(); + if (commentDoc.containsKey("_id")) { + comment.setId(commentDoc.getObjectId("_id").toString()); + } + comment.setContent(commentDoc.getString("content")); + comment.setAuthor(commentDoc.getString("author")); + comment.setAuthorName(commentDoc.getString("authorName")); + if (commentDoc.containsKey("createdAt")) { + Date commentCreatedAt = commentDoc.getDate("createdAt"); + comment.setCreatedAt(LocalDateTime.ofInstant(commentCreatedAt.toInstant(), ZoneId.systemDefault())); + } + comments.add(comment); + } + } + reply.setComments(comments); + } + + replies.add(reply); + } + } + inquiry.setReplies(replies); + } + + return inquiry; + } + + /** + * 게시글 작성 + */ + public boolean create(InquiryDTO inquiry) { + try { + System.out.println("=== 게시글 작성 시작 ==="); + System.out.println("Title: " + inquiry.getTitle()); + System.out.println("Author: " + inquiry.getAuthor()); + + Document doc = inquiryToDocument(inquiry); + System.out.println("저장할 Document: " + doc.toJson()); + inquiryCollection.insertOne(doc); + System.out.println("게시글 작성 성공!"); + return true; + } catch (Exception e) { + System.out.println("게시글 작성 오류 발생:"); + e.printStackTrace(); + return false; + } + } + + /** + * ID로 게시글 조회 (조회수 증가) + */ + public InquiryDTO findById(String id) { + try { + Document doc = inquiryCollection.find(Filters.eq("_id", new ObjectId(id))).first(); + if (doc != null) { + // 조회수 증가 + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(id)), + Updates.inc("viewCount", 1) + ); + // 조회수 증가된 값으로 다시 조회 + doc = inquiryCollection.find(Filters.eq("_id", new ObjectId(id))).first(); + } + return documentToInquiry(doc); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * ID로 게시글 조회 (조회수 증가 없음) + */ + public InquiryDTO findByIdWithoutIncrement(String id) { + try { + Document doc = inquiryCollection.find(Filters.eq("_id", new ObjectId(id))).first(); + return documentToInquiry(doc); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 게시글 목록 조회 (페이징) + */ + public List findAll(int page, int pageSize, String searchType, String searchKeyword) { + List inquiries = new ArrayList<>(); + try { + com.mongodb.client.FindIterable findIterable; + + // 검색 조건 설정 + if (searchKeyword != null && !searchKeyword.trim().isEmpty()) { + if ("title".equals(searchType)) { + findIterable = inquiryCollection.find( + Filters.regex("title", searchKeyword, "i") + ).sort(Sorts.descending("createdAt")); + } else if ("content".equals(searchType)) { + findIterable = inquiryCollection.find( + Filters.regex("content", searchKeyword, "i") + ).sort(Sorts.descending("createdAt")); + } else { + // 제목 또는 내용 검색 + findIterable = inquiryCollection.find( + Filters.or( + Filters.regex("title", searchKeyword, "i"), + Filters.regex("content", searchKeyword, "i") + ) + ).sort(Sorts.descending("createdAt")); + } + } else { + findIterable = inquiryCollection.find().sort(Sorts.descending("createdAt")); + } + + // 페이징 적용 + int skip = (page - 1) * pageSize; + findIterable = findIterable.skip(skip).limit(pageSize); + + for (Document doc : findIterable) { + inquiries.add(documentToInquiry(doc)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return inquiries; + } + + /** + * 전체 게시글 수 조회 (검색 조건 포함) + */ + public int getTotalCount(String searchType, String searchKeyword) { + try { + long count; + if (searchKeyword != null && !searchKeyword.trim().isEmpty()) { + if ("title".equals(searchType)) { + count = inquiryCollection.countDocuments( + Filters.regex("title", searchKeyword, "i") + ); + } else if ("content".equals(searchType)) { + count = inquiryCollection.countDocuments( + Filters.regex("content", searchKeyword, "i") + ); + } else { + count = inquiryCollection.countDocuments( + Filters.or( + Filters.regex("title", searchKeyword, "i"), + Filters.regex("content", searchKeyword, "i") + ) + ); + } + } else { + count = inquiryCollection.countDocuments(); + } + return (int) count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 게시글 수정 + */ + public boolean update(InquiryDTO inquiry) { + try { + System.out.println("=== 게시글 수정 ==="); + System.out.println("ID: " + inquiry.getId()); + System.out.println("Title: " + inquiry.getTitle()); + + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(inquiry.getId())), + Updates.combine( + Updates.set("title", inquiry.getTitle()), + Updates.set("content", inquiry.getContent()) + ) + ); + System.out.println("게시글 수정 성공!"); + return true; + } catch (Exception e) { + System.out.println("게시글 수정 오류:"); + e.printStackTrace(); + return false; + } + } + + /** + * 게시글 삭제 + */ + public boolean delete(String id) { + try { + inquiryCollection.deleteOne(Filters.eq("_id", new ObjectId(id))); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 답변 추가 + */ + public boolean addReply(String inquiryId, InquiryDTO.ReplyDTO reply) { + try { + System.out.println("=== 답변 추가 ==="); + System.out.println("Inquiry ID: " + inquiryId); + System.out.println("Reply Author: " + reply.getAuthor()); + + Document replyDoc = new Document(); + replyDoc.append("_id", new ObjectId()) + .append("content", reply.getContent()) + .append("author", reply.getAuthor()) + .append("authorName", reply.getAuthorName()) + .append("createdAt", Date.from(reply.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(inquiryId)), + Updates.combine( + Updates.push("replies", replyDoc), + Updates.set("status", "답변완료") + ) + ); + System.out.println("답변 추가 성공!"); + return true; + } catch (Exception e) { + System.out.println("답변 추가 오류:"); + e.printStackTrace(); + return false; + } + } + + /** + * 답변 삭제 + */ + public boolean deleteReply(String inquiryId, String replyId) { + try { + // 답변을 제거하고 상태를 다시 확인 + InquiryDTO inquiry = findByIdWithoutIncrement(inquiryId); + if (inquiry != null && inquiry.getReplies() != null) { + List updatedReplies = new ArrayList<>(); + for (InquiryDTO.ReplyDTO reply : inquiry.getReplies()) { + if (!reply.getId().equals(replyId)) { + Document replyDoc = new Document(); + if (reply.getId() != null) { + replyDoc.append("_id", new ObjectId(reply.getId())); + } + replyDoc.append("content", reply.getContent()) + .append("author", reply.getAuthor()) + .append("authorName", reply.getAuthorName()) + .append("createdAt", Date.from(reply.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + updatedReplies.add(replyDoc); + } + } + + // 답변이 없으면 상태를 "답변대기"로 변경 + String newStatus = updatedReplies.isEmpty() ? "답변대기" : "답변완료"; + + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(inquiryId)), + Updates.combine( + Updates.set("replies", updatedReplies), + Updates.set("status", newStatus) + ) + ); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 댓글 추가 (답변에 대한 댓글) + */ + public boolean addComment(String inquiryId, String replyId, InquiryDTO.CommentDTO comment) { + try { + System.out.println("=== 댓글 추가 ==="); + System.out.println("Inquiry ID: " + inquiryId); + System.out.println("Reply ID: " + replyId); + System.out.println("Comment Author: " + comment.getAuthor()); + + // 게시글 조회 + InquiryDTO inquiry = findByIdWithoutIncrement(inquiryId); + if (inquiry == null || inquiry.getReplies() == null) { + return false; + } + + // 해당 답변 찾기 + InquiryDTO.ReplyDTO targetReply = null; + for (InquiryDTO.ReplyDTO reply : inquiry.getReplies()) { + if (reply.getId().equals(replyId)) { + targetReply = reply; + break; + } + } + + if (targetReply == null) { + return false; + } + + // 댓글 추가 + Document commentDoc = new Document(); + commentDoc.append("_id", new ObjectId()) + .append("content", comment.getContent()) + .append("author", comment.getAuthor()) + .append("authorName", comment.getAuthorName()) + .append("createdAt", Date.from(comment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + + // MongoDB 배열 업데이트를 위해 replies 배열을 다시 구성 + List updatedReplies = new ArrayList<>(); + for (InquiryDTO.ReplyDTO reply : inquiry.getReplies()) { + Document replyDoc = new Document(); + if (reply.getId() != null) { + replyDoc.append("_id", new ObjectId(reply.getId())); + } + replyDoc.append("content", reply.getContent()) + .append("author", reply.getAuthor()) + .append("authorName", reply.getAuthorName()) + .append("createdAt", Date.from(reply.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + + // 해당 답변에 댓글 추가 + if (reply.getId().equals(replyId)) { + List commentDocs = new ArrayList<>(); + if (reply.getComments() != null) { + for (InquiryDTO.CommentDTO existingComment : reply.getComments()) { + Document existingCommentDoc = new Document(); + if (existingComment.getId() != null) { + existingCommentDoc.append("_id", new ObjectId(existingComment.getId())); + } + existingCommentDoc.append("content", existingComment.getContent()) + .append("author", existingComment.getAuthor()) + .append("authorName", existingComment.getAuthorName()) + .append("createdAt", Date.from(existingComment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + commentDocs.add(existingCommentDoc); + } + } + commentDocs.add(commentDoc); + replyDoc.append("comments", commentDocs); + } else if (reply.getComments() != null && !reply.getComments().isEmpty()) { + // 다른 답변의 댓글도 유지 + List commentDocs = new ArrayList<>(); + for (InquiryDTO.CommentDTO existingComment : reply.getComments()) { + Document existingCommentDoc = new Document(); + if (existingComment.getId() != null) { + existingCommentDoc.append("_id", new ObjectId(existingComment.getId())); + } + existingCommentDoc.append("content", existingComment.getContent()) + .append("author", existingComment.getAuthor()) + .append("authorName", existingComment.getAuthorName()) + .append("createdAt", Date.from(existingComment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + commentDocs.add(existingCommentDoc); + } + replyDoc.append("comments", commentDocs); + } + + updatedReplies.add(replyDoc); + } + + // MongoDB 업데이트 + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(inquiryId)), + Updates.set("replies", updatedReplies) + ); + + System.out.println("댓글 추가 성공!"); + return true; + } catch (Exception e) { + System.out.println("댓글 추가 오류:"); + e.printStackTrace(); + return false; + } + } + + /** + * 댓글 삭제 + */ + public boolean deleteComment(String inquiryId, String replyId, String commentId) { + try { + // 게시글 조회 + InquiryDTO inquiry = findByIdWithoutIncrement(inquiryId); + if (inquiry == null || inquiry.getReplies() == null) { + return false; + } + + // 해당 답변 찾기 및 댓글 제거 + List updatedReplies = new ArrayList<>(); + for (InquiryDTO.ReplyDTO reply : inquiry.getReplies()) { + Document replyDoc = new Document(); + if (reply.getId() != null) { + replyDoc.append("_id", new ObjectId(reply.getId())); + } + replyDoc.append("content", reply.getContent()) + .append("author", reply.getAuthor()) + .append("authorName", reply.getAuthorName()) + .append("createdAt", Date.from(reply.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + + // 해당 답변의 댓글 목록 업데이트 + if (reply.getId().equals(replyId) && reply.getComments() != null) { + List commentDocs = new ArrayList<>(); + for (InquiryDTO.CommentDTO comment : reply.getComments()) { + if (!comment.getId().equals(commentId)) { + Document commentDoc = new Document(); + if (comment.getId() != null) { + commentDoc.append("_id", new ObjectId(comment.getId())); + } + commentDoc.append("content", comment.getContent()) + .append("author", comment.getAuthor()) + .append("authorName", comment.getAuthorName()) + .append("createdAt", Date.from(comment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + commentDocs.add(commentDoc); + } + } + if (!commentDocs.isEmpty()) { + replyDoc.append("comments", commentDocs); + } + } else if (reply.getComments() != null && !reply.getComments().isEmpty()) { + // 다른 답변의 댓글 유지 + List commentDocs = new ArrayList<>(); + for (InquiryDTO.CommentDTO comment : reply.getComments()) { + Document commentDoc = new Document(); + if (comment.getId() != null) { + commentDoc.append("_id", new ObjectId(comment.getId())); + } + commentDoc.append("content", comment.getContent()) + .append("author", comment.getAuthor()) + .append("authorName", comment.getAuthorName()) + .append("createdAt", Date.from(comment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + commentDocs.add(commentDoc); + } + replyDoc.append("comments", commentDocs); + } + + updatedReplies.add(replyDoc); + } + + // MongoDB 업데이트 + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(inquiryId)), + Updates.set("replies", updatedReplies) + ); + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 게시글 댓글 추가 + */ + public boolean addInquiryComment(String inquiryId, InquiryDTO.CommentDTO comment) { + try { + System.out.println("=== 게시글 댓글 추가 ==="); + System.out.println("Inquiry ID: " + inquiryId); + System.out.println("Comment Author: " + comment.getAuthor()); + + Document commentDoc = new Document(); + commentDoc.append("_id", new ObjectId()) + .append("content", comment.getContent()) + .append("author", comment.getAuthor()) + .append("authorName", comment.getAuthorName()) + .append("createdAt", Date.from(comment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(inquiryId)), + Updates.push("comments", commentDoc) + ); + + System.out.println("게시글 댓글 추가 성공!"); + return true; + } catch (Exception e) { + System.out.println("게시글 댓글 추가 오류:"); + e.printStackTrace(); + return false; + } + } + + /** + * 게시글 댓글 삭제 + */ + public boolean deleteInquiryComment(String inquiryId, String commentId) { + try { + // 게시글 조회 + InquiryDTO inquiry = findByIdWithoutIncrement(inquiryId); + if (inquiry == null || inquiry.getComments() == null) { + return false; + } + + // 댓글 목록에서 해당 댓글 제거 + List updatedComments = new ArrayList<>(); + for (InquiryDTO.CommentDTO comment : inquiry.getComments()) { + if (!comment.getId().equals(commentId)) { + Document commentDoc = new Document(); + if (comment.getId() != null) { + commentDoc.append("_id", new ObjectId(comment.getId())); + } + commentDoc.append("content", comment.getContent()) + .append("author", comment.getAuthor()) + .append("authorName", comment.getAuthorName()) + .append("createdAt", Date.from(comment.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())); + updatedComments.add(commentDoc); + } + } + + // MongoDB 업데이트 + inquiryCollection.updateOne( + Filters.eq("_id", new ObjectId(inquiryId)), + Updates.set("comments", updatedComments) + ); + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } +} + diff --git a/WORK_OUT/src/main/java/dto/InquiryDTO.java b/WORK_OUT/src/main/java/dto/InquiryDTO.java new file mode 100644 index 0000000..d999b89 --- /dev/null +++ b/WORK_OUT/src/main/java/dto/InquiryDTO.java @@ -0,0 +1,298 @@ +package dto; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 고객센터 문의 게시글을 표현하는 DTO 클래스 + */ +public class InquiryDTO { + private String id; // 게시글 ID (Primary Key) + private String title; // 제목 + private String content; // 내용 + private String author; // 작성자 username + private String authorName; // 작성자 이름 + private LocalDateTime createdAt; // 작성일시 + private int viewCount; // 조회수 + private String status; // 상태 ("답변대기" / "답변완료") + private List replies; // 답변 목록 + private List comments; // 게시글 댓글 목록 + + public InquiryDTO() { + this.viewCount = 0; + this.status = "답변대기"; + this.replies = new ArrayList<>(); + this.comments = new ArrayList<>(); + this.createdAt = LocalDateTime.now(); + } + + public InquiryDTO(String title, String content, String author, String authorName) { + this(); + this.title = title; + this.content = content; + this.author = author; + this.authorName = authorName; + } + + // Getters and Setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public List getReplies() { + return replies; + } + + public void setReplies(List replies) { + this.replies = replies; + } + + public List getComments() { + return comments; + } + + public void setComments(List comments) { + this.comments = comments; + } + + /** + * 답변 추가 + */ + public void addReply(ReplyDTO reply) { + if (this.replies == null) { + this.replies = new ArrayList<>(); + } + this.replies.add(reply); + this.status = "답변완료"; + } + + /** + * 게시글 댓글 추가 + */ + public void addComment(CommentDTO comment) { + if (this.comments == null) { + this.comments = new ArrayList<>(); + } + this.comments.add(comment); + } + + /** + * 답변 내부 클래스 + */ + public static class ReplyDTO { + private String id; // 답변 ID + private String content; // 답변 내용 + private String author; // 답변 작성자 username + private String authorName; // 답변 작성자 이름 + private LocalDateTime createdAt; // 답변 작성일시 + private List comments; // 댓글 목록 + + public ReplyDTO() { + this.createdAt = LocalDateTime.now(); + this.comments = new ArrayList<>(); + } + + public ReplyDTO(String content, String author, String authorName) { + this(); + this.content = content; + this.author = author; + this.authorName = authorName; + } + + // Getters and Setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public List getComments() { + return comments; + } + + public void setComments(List comments) { + this.comments = comments; + } + + /** + * 댓글 추가 + */ + public void addComment(CommentDTO comment) { + if (this.comments == null) { + this.comments = new ArrayList<>(); + } + this.comments.add(comment); + } + } + + /** + * 댓글 내부 클래스 (답변에 대한 댓글) + */ + public static class CommentDTO { + private String id; // 댓글 ID + private String content; // 댓글 내용 + private String author; // 댓글 작성자 username + private String authorName; // 댓글 작성자 이름 + private LocalDateTime createdAt; // 댓글 작성일시 + + public CommentDTO() { + this.createdAt = LocalDateTime.now(); + } + + public CommentDTO(String content, String author, String authorName) { + this(); + this.content = content; + this.author = author; + this.authorName = authorName; + } + + // Getters and Setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + } + + @Override + public String toString() { + return "InquiryDTO{" + + "id='" + id + '\'' + + ", title='" + title + '\'' + + ", author='" + author + '\'' + + ", status='" + status + '\'' + + ", viewCount=" + viewCount + + ", createdAt=" + createdAt + + '}'; + } +} + diff --git a/WORK_OUT/src/main/webapp/boardDetail.jsp b/WORK_OUT/src/main/webapp/boardDetail.jsp new file mode 100644 index 0000000..56cf085 --- /dev/null +++ b/WORK_OUT/src/main/webapp/boardDetail.jsp @@ -0,0 +1,191 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + Inquiry Detail - WORK OUT + + + +<%@ include file="header.jsp" %> + +
+ +
+ ${error} +
+
+ + + +
+
+

${inquiry.title}

+
+ Author: ${inquiry.authorName} + Date: ${inquiry.createdAt.toLocalDate()} ${inquiry.createdAt.toLocalTime().toString().substring(0, 5)} + Views: ${inquiry.viewCount} + + Status: + + ${inquiry.status == '답변완료' ? 'Answered' : 'Pending'} + + +
+
+ +
+ ${inquiry.content} +
+ + + +
+ Edit + Delete +
+
+
+ + + +
+

Comments

+
+ +
+
+
+ ${comment.authorName} + + (${comment.createdAt.toLocalDate()} ${comment.createdAt.toLocalTime().toString().substring(0, 5)}) + +
+ + Delete + +
+
${comment.content}
+
+
+
+
+
+ + + +
+

Write Comment

+
+ + + +
+ +
+
+
+
+ + + +
+

Replies

+
+ +
+
+
+ ${reply.authorName} + + (${reply.createdAt.toLocalDate()} ${reply.createdAt.toLocalTime().toString().substring(0, 5)}) + +
+ + Delete + +
+
${reply.content}
+ + + +
+

Comments

+ +
+
+
+ ${comment.authorName} + + (${comment.createdAt.toLocalDate()} ${comment.createdAt.toLocalTime().toString().substring(0, 5)}) + +
+ + Delete + +
+
${comment.content}
+
+
+
+
+ + + +
+
+ + + + + +
+
+
+
+
+
+
+
+ + + +
+

Write Reply

+
+ + + +
+ +
+
+
+
+
+ + + +
+ +<%@ include file="footer.jsp" %> + + + diff --git a/WORK_OUT/src/main/webapp/boardList.jsp b/WORK_OUT/src/main/webapp/boardList.jsp new file mode 100644 index 0000000..eb05729 --- /dev/null +++ b/WORK_OUT/src/main/webapp/boardList.jsp @@ -0,0 +1,118 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + Customer Service - WORK OUT + + + +<%@ include file="header.jsp" %> + +
+
+

CUSTOMER SERVICE

+

Please check the inquiries below or create a new inquiry.

+
+ + +
+ ${error} +
+
+ + +
+
+ + + + +
+ + Write Inquiry + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
NoTitleAuthorDateViewsStatus
${totalCount - ((currentPage - 1) * pageSize) - status.index} + + ${inquiry.title} + + ${inquiry.authorName} + ${inquiry.createdAt.toLocalDate()} + ${inquiry.viewCount} + + ${inquiry.status == '답변완료' ? 'Answered' : 'Pending'} + +
+ + + +
+ +
+

No inquiries found.

+ + Write First Inquiry + +
+
+
+
+ +<%@ include file="footer.jsp" %> + + + diff --git a/WORK_OUT/src/main/webapp/boardWrite.jsp b/WORK_OUT/src/main/webapp/boardWrite.jsp new file mode 100644 index 0000000..d7cca3c --- /dev/null +++ b/WORK_OUT/src/main/webapp/boardWrite.jsp @@ -0,0 +1,54 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + ${inquiry != null ? 'Edit Inquiry' : 'Write Inquiry'} - WORK OUT + + + +<%@ include file="header.jsp" %> + +
+
+

${inquiry != null ? 'EDIT INQUIRY' : 'WRITE INQUIRY'}

+

${inquiry != null ? 'Please edit your inquiry below.' : 'Please fill in the form below to create a new inquiry.'}

+
+ + +
+ ${error} +
+
+ +
+
+ + + + + +
+ + +
+ +
+ + +
+ +
+ Cancel + +
+
+
+
+ +<%@ include file="footer.jsp" %> + + + diff --git a/WORK_OUT/src/main/webapp/css/boardstyle.css b/WORK_OUT/src/main/webapp/css/boardstyle.css new file mode 100644 index 0000000..1a35f95 --- /dev/null +++ b/WORK_OUT/src/main/webapp/css/boardstyle.css @@ -0,0 +1,531 @@ +@charset "UTF-8"; + +/* 게시판 컨테이너 */ +.board-container { + max-width: 1200px; + margin: 0 auto; + padding: 30px 20px; + background-color: #ffffff; + min-height: calc(100vh - 200px); +} + +/* 게시판 헤더 */ +.board-header { + text-align: center; + margin-bottom: 40px; + padding-bottom: 20px; + border-bottom: 2px solid #667eea; +} + +.board-header h1 { + color: #2d2d3d; + font-size: 2.5em; + margin-bottom: 10px; +} + +.board-header p { + color: #666; + font-size: 1.1em; +} + +/* 검색 및 작성 버튼 영역 */ +.board-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + flex-wrap: wrap; + gap: 15px; +} + +.search-form { + display: flex; + gap: 10px; + flex: 1; + max-width: 600px; +} + +.search-form select { + padding: 10px 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + background-color: white; + cursor: pointer; +} + +.search-form input[type="text"] { + flex: 1; + padding: 10px 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; +} + +.search-form button { + padding: 10px 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: transform 0.2s, box-shadow 0.2s; +} + +.search-form button:hover { + transform: translateY(-2px); + box-shadow: 0 4px 10px rgba(102, 126, 234, 0.4); +} + +.write-btn { + padding: 10px 25px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + text-decoration: none; + display: inline-block; + transition: transform 0.2s, box-shadow 0.2s; +} + +.write-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 10px rgba(102, 126, 234, 0.4); +} + +/* 게시글 목록 테이블 */ +.board-table { + width: 100%; + border-collapse: collapse; + margin-bottom: 30px; + background-color: white; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + border-radius: 8px; + overflow: hidden; +} + +.board-table thead { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.board-table th { + padding: 15px; + text-align: left; + font-weight: 600; + font-size: 14px; +} + +.board-table td { + padding: 15px; + border-bottom: 1px solid #eee; + font-size: 14px; +} + +.board-table tbody tr:hover { + background-color: #f8f9fa; + cursor: pointer; +} + +.board-table tbody tr:last-child td { + border-bottom: none; +} + +/* 게시글 제목 링크 */ +.board-title-link { + color: #2d2d3d; + text-decoration: none; + font-weight: 500; + transition: color 0.2s; +} + +.board-title-link:hover { + color: #667eea; +} + +/* 상태 배지 */ +.status-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; +} + +.status-waiting { + background-color: #ffeaa7; + color: #d63031; +} + +.status-completed { + background-color: #d5f4e6; + color: #00b894; +} + +/* 페이징 */ +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + margin-top: 30px; +} + +.pagination a, +.pagination span { + padding: 8px 15px; + border: 1px solid #ddd; + border-radius: 5px; + text-decoration: none; + color: #2d2d3d; + transition: all 0.2s; +} + +.pagination a:hover { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-color: transparent; +} + +.pagination .current { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-color: transparent; +} + +/* 게시글 상세보기 */ +.board-detail { + background-color: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + margin-bottom: 30px; +} + +.detail-header { + border-bottom: 2px solid #eee; + padding-bottom: 20px; + margin-bottom: 20px; +} + +.detail-title { + font-size: 1.8em; + color: #2d2d3d; + margin-bottom: 15px; +} + +.detail-meta { + display: flex; + gap: 20px; + color: #666; + font-size: 14px; + flex-wrap: wrap; +} + +.detail-meta span { + display: flex; + align-items: center; + gap: 5px; +} + +.detail-content { + padding: 20px 0; + line-height: 1.8; + color: #333; + white-space: pre-wrap; + word-wrap: break-word; + min-height: 200px; +} + +.detail-actions { + display: flex; + gap: 10px; + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid #eee; +} + +.detail-btn { + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + text-decoration: none; + display: inline-block; + transition: all 0.2s; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 10px rgba(102, 126, 234, 0.4); +} + +.btn-secondary { + background-color: #6c757d; + color: white; +} + +.btn-secondary:hover { + background-color: #5a6268; +} + +.btn-danger { + background-color: #e63946; + color: white; +} + +.btn-danger:hover { + background-color: #d62839; +} + +/* 게시글 작성/수정 폼 */ +.board-form { + background-color: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + color: #2d2d3d; + font-weight: 600; + font-size: 14px; +} + +.form-group input[type="text"], +.form-group textarea { + width: 100%; + padding: 12px 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + font-family: inherit; + transition: border-color 0.2s; +} + +.form-group input[type="text"]:focus, +.form-group textarea:focus { + outline: none; + border-color: #667eea; +} + +.form-group textarea { + min-height: 300px; + resize: vertical; +} + +.form-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + margin-top: 30px; +} + +/* 답변 영역 */ +.reply-section { + margin-top: 30px; + padding-top: 30px; + border-top: 2px solid #eee; +} + +.reply-list { + margin-bottom: 30px; +} + +.reply-item { + background-color: #f8f9fa; + padding: 20px; + border-radius: 8px; + margin-bottom: 15px; + border-left: 4px solid #667eea; +} + +.reply-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + flex-wrap: wrap; + gap: 10px; +} + +.reply-author { + font-weight: 600; + color: #667eea; +} + +.reply-date { + color: #666; + font-size: 12px; +} + +.reply-content { + color: #333; + line-height: 1.6; + white-space: pre-wrap; + word-wrap: break-word; +} + +.reply-form { + background-color: #f8f9fa; + padding: 20px; + border-radius: 8px; +} + +.reply-form textarea { + width: 100%; + padding: 12px 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + font-family: inherit; + min-height: 150px; + resize: vertical; + margin-bottom: 15px; +} + +/* 댓글 스타일 */ +.comment-list { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #eee; +} + +.comment-item { + background-color: #f8f9fa; + padding: 15px; + border-radius: 5px; + margin-bottom: 10px; + border-left: 3px solid #667eea; +} + +.comment-item .comment-author { + font-weight: 600; + color: #667eea; + font-size: 13px; +} + +.comment-item .comment-date { + color: #999; + font-size: 12px; + margin-left: 10px; +} + +.comment-item .comment-content { + color: #333; + font-size: 13px; + line-height: 1.6; + white-space: pre-wrap; + word-wrap: break-word; + margin-top: 8px; +} + +.comment-form { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #eee; +} + +.comment-form form { + display: flex; + gap: 10px; + align-items: flex-start; +} + +.comment-form textarea { + flex: 1; + padding: 10px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 13px; + font-family: inherit; + min-height: 80px; + resize: vertical; +} + +.comment-form button { + padding: 10px 20px; + font-size: 13px; + white-space: nowrap; +} + +/* 에러 메시지 */ +.error-message { + background-color: #ffe5e5; + color: #d63031; + padding: 15px; + border-radius: 5px; + margin-bottom: 20px; + border-left: 4px solid #d63031; +} + +/* 빈 게시글 메시지 */ +.empty-message { + text-align: center; + padding: 60px 20px; + color: #666; +} + +.empty-message p { + font-size: 1.2em; + margin-bottom: 20px; +} + +/* 반응형 디자인 */ +@media (max-width: 768px) { + .board-container { + padding: 20px 10px; + } + + .board-header h1 { + font-size: 2em; + } + + .board-actions { + flex-direction: column; + align-items: stretch; + } + + .search-form { + max-width: 100%; + } + + .board-table { + font-size: 12px; + } + + .board-table th, + .board-table td { + padding: 10px 8px; + } + + .detail-meta { + flex-direction: column; + gap: 10px; + } + + .detail-actions { + flex-direction: column; + } + + .detail-btn { + width: 100%; + text-align: center; + } +} + diff --git a/WORK_OUT/src/main/webapp/customerService.jsp b/WORK_OUT/src/main/webapp/customerService.jsp index cbb4ae7..4af8115 100644 --- a/WORK_OUT/src/main/webapp/customerService.jsp +++ b/WORK_OUT/src/main/webapp/customerService.jsp @@ -1,81 +1,6 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - - 고객센터 - WORK OUT - - - -<%@ include file="header.jsp" %> - -
-
-

CUSTOMER CENTRE

-

PLEASE READ THE FAQs BELOW OR CONTACT US USING THE FORM.

-
- - -
-

FAQ

-
-
HOW DO I SIGN UP?
-
YOU CAN SIGN UP BY CLICKING 'SIGNUP' BUTTON ABOVE.
-
-
-
I FORGOT MY PASSWORD.
-
YOU CAN FIND YOUR PASSWORD BY CLICKING 'FIND PASSWORD' BUTTON IN LOGIN PAGE.
-
-
-
ARE THE EXERCISE PROGRAMS FREE OF CHARGE?
-
BASIC WORK-OUT PROGRAMS ARE FREE. HOWEVER, THERE ARE SOME EXCEPTIONAL PREMIUM PROGRAMS THAT YOU WOULD NEED TO PAY.
-
-
- - -
-

CONTACT US

-
- - - - - - - - - - -
-
-
- - - - -<%@ include file="footer.jsp" %> - - +<% + // 게시판 목록으로 리다이렉트 + response.sendRedirect(request.getContextPath() + "/inquiry?action=list"); +%>