localhost-back/src/main/java/io/company/localhost/controller/api/BoardController.java

415 lines
14 KiB
Java

/************************************************************
*
* @packageName : io.company.localhost.controller.api
* @fileName : BoardController.java
* @author : 서지희
* @date : 25.01.07
* @description : 게시판
*
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25.01.07 서지희 최초 생성
*
*************************************************************/
package io.company.localhost.controller.api;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.github.pagehelper.PageInfo;
import io.company.localhost.common.annotation.Member;
import io.company.localhost.common.annotation.ParameterCheck;
import io.company.localhost.common.annotation.ReqMap;
import io.company.localhost.common.dto.ApiResponse;
import io.company.localhost.common.dto.MapDto;
import io.company.localhost.common.exception.NotFoundHandler;
import io.company.localhost.service.commoncodService;
import io.company.localhost.service.localbordService;
import io.company.localhost.utils.AuthUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/api/board")
@RequiredArgsConstructor
@Slf4j
public class BoardController {
private final localbordService boardService;
private final commoncodService commoncodService;
private final PasswordEncoder passwordEncoder;
/**
* 공지사항 목록 조회
* @ReqMap map 요청 파라미터 (searchKeyword)
* @return 전체 공지사항 목록
*/
@Member
@ParameterCheck
@GetMapping("/notices")
public ApiResponse<List<MapDto>> getNotices(@ReqMap MapDto map) {
// size를 안전하게 Integer로 변환하여 MapDto에 다시 넣기
Object sizeObj = map.get("size");
Integer size = null;
if (sizeObj instanceof String) {
size = Integer.parseInt((String) sizeObj);
}else {
size = null;
}
map.put("size", size);
return ApiResponse.ok(boardService.selectNotices(map));
}
/**
* 자유/익명 게시판 목록 조회
* @ReqMap map 요청 파라미터 (page, searchKeyword, orderBy, size)
* @return 페이징된 자유/익명 게시판 목록
*/
@Member
@ParameterCheck
@GetMapping("/general")
public ApiResponse<PageInfo<MapDto>> getGeneralPosts(@ReqMap MapDto map) {
return ApiResponse.ok(boardService.selectGeneralPosts(map));
}
/**
* 게시물 작성
* @ReqMap map 요청 파라미터 (LOCBRDTTL, LOCBRDCON, MEMBERSEQ, LOCBRDTYP,
* LOCBRDPWD(익명일 때만), LOCBRDCAT(지식커뮤니티만))
* @return 작성된 게시물의 ID
*/
@Member
@ParameterCheck
@PostMapping
public ApiResponse<BigInteger> createBoard(@ReqMap MapDto map) {
if (map.containsKey("LOCBRDPWD") && !map.getString("LOCBRDPWD").trim().isEmpty()) { // 빈 값 체크
String rawPassword = map.getString("LOCBRDPWD");
String hashedPassword = passwordEncoder.encode(rawPassword);
map.put("LOCBRDPWD", hashedPassword);
}
return ApiResponse.ok(boardService.insertBoard(map));
}
/**
* 게시물 상세보기
* @param boardId 게시물 ID
* @return 게시물 상세정보
*/
@Member
@ParameterCheck
@GetMapping("/{boardId}")
public ApiResponse<MapDto> getBoardDetail(@PathVariable("boardId") Long boardId) {
MapDto board = boardService.selectBoardDetail(boardId);
if (board == null) {
//throw new NotFoundHandler("게시물 ID " + boardId + "을(를) 찾을 수 없습니다.");
String errMessage = "게시물 ID " + boardId + "을(를) 찾을 수 없습니다.";
return ApiResponse.error(HttpStatus.NOT_FOUND, errMessage);
}
// 📌 첨부파일 목록 추가
List<MapDto> attachments = boardService.selectAttachments(boardId);
board.put("attachments", attachments != null ? attachments : new ArrayList<>());
return ApiResponse.ok(board);
}
/**
* 게시물 수정 조회(익명 게시글은 비밀번호 값 필수)
*
* @param boardId
* @param map
* @return
*/
@Member
@ParameterCheck
@PostMapping("/{boardId}")
public ApiResponse<MapDto> getBoardDetail2(@PathVariable("boardId") Long boardId, @ReqMap MapDto map) {
map.put("boardId", boardId);
return boardService.selectBoardDetail2(map);
}
/**
* 파일 다운로드 API
* @param path 파일 경로
* @return 파일 데이터 (바이너리 응답)
*/
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam String path) {
try {
Path filePath = Paths.get(path).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists() || !resource.isReadable()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
String contentType = Files.probeContentType(filePath);
String fileName = filePath.getFileName().toString();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
.header(HttpHeaders.CONTENT_TYPE, contentType != null ? contentType : "application/octet-stream")
.body(resource);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 게시물 삭제
* @ReqMap map 수정 데이터 (LOCBRDSEQ)
* @return 삭제 결과 메시지
*/
@Member
@ParameterCheck
@DeleteMapping("/{boardId}")
public ApiResponse<String> deleteBoard(@ReqMap MapDto map) {
return boardService.deleteBoard(map);
}
/**
* 게시물 수정
*
* @param map 수정 데이터 (LOCBRDTTL, LOCBRDCON, LOCBRDSEQ, delListInfo)
* @param files 첨부파일 정보
* @return
* @throws IOException
*/
@Member
@PutMapping("/{boardId}")
public ApiResponse<String> updateBoard(@ReqMap MapDto map, @RequestPart(value = "files", required = false) List<MultipartFile> files) throws IOException {
return boardService.updateBoardWithFiles(map, files);
}
/**
* 첨부파일 추가
* @ReqMap map 요청 파라미터 (CMNFLEREG, CMNFLESIZ, CMNFLEEXT, CMNFLEORG, CMNFLENAM, CMNFLEPAT, CMNBRDSEQ)
* @return 첨부파일 저장 결과 메시지
*/
@Member
@ParameterCheck
@PostMapping("/{CMNBRDSEQ}/attachments")
public ApiResponse<String> uploadAttachment(@ReqMap MapDto map, @RequestParam("file") MultipartFile file) {
try {
Long userId = AuthUtil.getUser().getId();
map.put("CMNFLEREG", userId);
boardService.insertAttachment(map, file);
return ApiResponse.ok("첨부파일이 저장되었습니다.");
} catch (Exception e) {
return ApiResponse.ok("첨부파일 저장 실패: " + e.getMessage());
}
}
/**
* 게시물, 댓글 좋아요/싫어요 추가
* @ReqMap map 데이터 (LOCCMTSEQ, MEMBERSEQ, LOCGOBGOD, LOCGOBBAD, LOCBRDSEQ)
* @return 반응 추가 결과 메시지
*/
@Member
@ParameterCheck
@PostMapping("/{LOCBRDSEQ}/{LOCCMTSEQ}/reaction")
public ApiResponse<String> reactToBoard(@ReqMap MapDto map) {
Long userId = AuthUtil.getUser().getId();
map.put("MEMBERSEQ", userId);
boardService.procReactToBoard(map);
return ApiResponse.ok("반응이 성공적으로 처리되었습니다.");
}
/**
* 댓글 조회
* @ReqMap map 수정 데이터 (LOCBRDSEQ, page)
* @return 댓글
*/
@Member
@ParameterCheck
@GetMapping("/{boardId}/comments")
public ApiResponse<PageInfo<MapDto>> selectComments(@ReqMap MapDto map) {
// 댓글조회
PageInfo<MapDto> comments = boardService.selectComments(map);
return ApiResponse.ok(comments);
}
/**
* 대댓글 조회
* @ReqMap map 수정 데이터 (LOCCMTPNT)
* @return 대댓글
*/
@Member
@ParameterCheck
@GetMapping("/{boardId}/reply")
public ApiResponse<List<MapDto>> getComments(@ReqMap MapDto map) {
List<MapDto> replies = boardService.selectReply(map);
return ApiResponse.ok(replies);
}
/**
* 댓글/대댓글 작성
* @param boardId 게시물 ID
* @ReqMap map 댓글 데이터 (LOCBRDSEQ, LOCCMTRPY, LOCCMTPNT, LOCCMTPWD, MEMBERSEQ )
* @return 작성 결과 메시지
*/
@Member
@ParameterCheck
@PostMapping("/{LOCBRDSEQ}/comment")
public ApiResponse<String> addCommentOrReply(@ReqMap MapDto map) {
if (map.containsKey("LOCCMTPWD") && !map.getString("LOCCMTPWD").trim().isEmpty()) { // 빈 값 체크
String rawPassword = map.getString("LOCCMTPWD");
String hashedPassword = passwordEncoder.encode(rawPassword);
map.put("LOCCMTPWD", hashedPassword);
}
boardService.insertCommentOrReply(map);
return ApiResponse.ok("댓글 또는 대댓글이 작성되었습니다.");
}
/**
* 댓글/대댓글 수정
* @param commentId 댓글 ID
* @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTRPY)
* @return 수정 결과 메시지
*/
@Member
@ParameterCheck
@PutMapping("/comment/{commentId}")
public ApiResponse<String> updateComment(@ReqMap MapDto map) {
boardService.updateComment(map);
return ApiResponse.ok("댓글이 수정되었습니다.");
}
/**
* 댓글/대댓글 삭제
* @param commentId 댓글 ID
* @return 삭제 결과 메시지
*/
@Member
@ParameterCheck
@DeleteMapping("/comment/{commentId}")
public ApiResponse<String> deleteComment(@PathVariable("commentId") Long commentId, @RequestParam(value = "LOCCMTPNT") Long parentId) {
MapDto map = new MapDto();
map.put("LOCCMTSEQ", commentId);
map.put("LOCCMTPNT", parentId);
boardService.deleteComment(map);
return ApiResponse.ok("댓글이 삭제되었습니다.");
}
/**
* 게시물 비밀번호 확인 (해싱된 비밀번호 비교 적용)
* @ReqMap map 수정 데이터 (LOCBRDSEQ, LOCBRDPWD)
* @return 비밀번호 확인 결과
*/
@Member
@ParameterCheck
@PostMapping("/{boardId}/password")
public ApiResponse<Boolean> checkBoardPassword(@ReqMap MapDto map) {
// boardId 데이터 타입 변환
Object boardIdObj = map.get("LOCBRDSEQ");
int boardId = 0;
if (boardIdObj instanceof Integer) {
boardId = (Integer) boardIdObj;
} else if (boardIdObj instanceof String) {
boardId = Integer.parseInt((String) boardIdObj);
}
String rawPassword = map.getString("LOCBRDPWD");
String storedHashedPassword = boardService.selectBoardPassword(boardId);
if (storedHashedPassword == null) {
throw new NotFoundHandler("해당 게시물이 존재하지 않습니다.");
}
boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword);
if (!isMatch) {
//throw new InvalidPasswordException("비밀번호가 일치하지 않습니다.");
return ApiResponse.error(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
}
return ApiResponse.ok(true);
}
/**
* 댓글 비밀번호 확인 (해싱된 비밀번호 비교 적용)
* @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTPWD)
* @return 비밀번호 확인 결과
*/
@Member
@ParameterCheck
@PostMapping("/comment/{commentId}/password")
public ApiResponse<Boolean> checkCommentPassword(@ReqMap MapDto map) {
// commentId 데이터 타입 변환
Object commentIdObj = map.get("LOCCMTSEQ");
int commentId = 0;
if (commentIdObj instanceof Integer) {
commentId = (Integer) commentIdObj;
} else if (commentIdObj instanceof String) {
commentId = Integer.parseInt((String) commentIdObj);
}
String rawPassword = map.getString("LOCCMTPWD");
String storedHashedPassword = boardService.selectCommentPassword(commentId);
if (storedHashedPassword == null) {
throw new NotFoundHandler("해당 댓글이 존재하지 않습니다.");
}
boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword);
if (!isMatch) {
//throw new InvalidPasswordException("비밀번호가 일치하지 않습니다.");
return ApiResponse.error(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
}
return ApiResponse.ok(true);
}
/**
* 카테고리 목록 조회
* @return 카테고리 리스트
*/
@GetMapping("/categories")
public ApiResponse<List<MapDto>> SelectCategories() {
List<MapDto> categories = commoncodService.selectCategoryList();
return ApiResponse.ok(categories);
}
}