게시물 삭제 기능 추가.
parent
93da80a1ff
commit
bde144db79
|
|
@ -8,22 +8,20 @@ import java.util.HashMap;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public enum LogStatus {
|
public enum LogStatus {
|
||||||
WRITE("WRITE", "작성"),
|
WRITE("작성"),
|
||||||
MODIFY("MODIFY", "수정"),
|
MODIFY("수정"),
|
||||||
MOVE("MOVE", "이동"),
|
MOVE("이동"),
|
||||||
DELETE("DELETE", "삭제"),
|
DELETE("삭제"),
|
||||||
FILE_ADD("FILE_ADD", "파일추가"),
|
FILE_ADD("파일추가"),
|
||||||
FILE_REMOVE("FILE_REMOVE", "파일삭제"),
|
FILE_REMOVE("파일삭제"),
|
||||||
FILE_DOWN("FILE_DOWN", "파일다운로드");
|
FILE_DOWN("파일다운로드");
|
||||||
|
|
||||||
private String key;
|
|
||||||
private String value;
|
private String value;
|
||||||
|
|
||||||
|
|
||||||
public static HashMap<String, String> getStatusMap(){
|
public static HashMap<String, String> getStatusMap(){
|
||||||
HashMap<String, String> statusMap = new HashMap<>();
|
HashMap<String, String> statusMap = new HashMap<>();
|
||||||
for(LogStatus status: LogStatus.values()){
|
for(LogStatus status: LogStatus.values()){
|
||||||
statusMap.put(status.getKey(), status.getValue());
|
statusMap.put(status.name(), status.getValue());
|
||||||
}
|
}
|
||||||
return statusMap;
|
return statusMap;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http.authorizeRequests() // 페이지 권한 설정
|
http.authorizeRequests() // 페이지 권한 설정
|
||||||
.antMatchers("/board/**").hasRole("MEMBER") // MEMBER, ADMIN만 접근 허용
|
.antMatchers("/board/**").hasRole(Role.USER.name()) // USER, ADMIN 접근 허용
|
||||||
.antMatchers("/admin/**").hasRole("ADMIN") // ADMIN만 접근 허용
|
.antMatchers("/admin/**").hasRole(Role.ADMIN.name()) // ADMIN만 접근 허용
|
||||||
.antMatchers("/user/login").permitAll() // 로그인 페이지는 권한 없이 접근 허용
|
.antMatchers("/user/login").permitAll() // 로그인 페이지는 권한 없이 접근 허용
|
||||||
.and() // 로그인 설정
|
.and() // 로그인 설정
|
||||||
.formLogin() .loginPage("/user/login") // Custom login form 사용
|
.formLogin() .loginPage("/user/login") // Custom login form 사용
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.dbnt.kcgfilemanager.model.UserInfo;
|
||||||
import com.dbnt.kcgfilemanager.service.BoardService;
|
import com.dbnt.kcgfilemanager.service.BoardService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||||
|
|
@ -62,10 +63,11 @@ public class BoardController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/selectBoardContent")
|
@GetMapping("/selectBoardContent")
|
||||||
public ModelAndView selectBoardContent(Board content){
|
public ModelAndView selectBoardContent(Board content, @AuthenticationPrincipal UserInfo loginUser){
|
||||||
ModelAndView mav = new ModelAndView("board/contentDetail");
|
ModelAndView mav = new ModelAndView("board/contentDetail");
|
||||||
Board target = boardService.selectContentByContentSeqAndViewCntUp(content.getContentSeq());
|
Board target = boardService.selectContentByContentSeqAndViewCntUp(content.getContentSeq());
|
||||||
mav.addObject("content", boardService.SelectContentForeignAttribute(target));
|
mav.addObject("content", boardService.SelectContentForeignAttribute(target));
|
||||||
|
mav.addObject("loginUser", loginUser);
|
||||||
return mav;
|
return mav;
|
||||||
}
|
}
|
||||||
@GetMapping("/selectBoardLog")
|
@GetMapping("/selectBoardLog")
|
||||||
|
|
@ -76,6 +78,10 @@ public class BoardController {
|
||||||
mav.addObject("logList", boardService.selectContentLog(content.getContentSeq()));
|
mav.addObject("logList", boardService.selectContentLog(content.getContentSeq()));
|
||||||
return mav;
|
return mav;
|
||||||
}
|
}
|
||||||
|
@DeleteMapping("/deleteContent")
|
||||||
|
public Integer deleteContent(Board content, @AuthenticationPrincipal UserInfo loginUser){
|
||||||
|
return boardService.deleteContent(content, loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/fileDownload")
|
@GetMapping("/fileDownload")
|
||||||
public void fileDownload(Principal principal, Integer contentSeq, Integer fileSeq, HttpServletRequest request, HttpServletResponse response){
|
public void fileDownload(Principal principal, Integer contentSeq, Integer fileSeq, HttpServletRequest request, HttpServletResponse response){
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package com.dbnt.kcgfilemanager.repository;
|
||||||
import com.dbnt.kcgfilemanager.model.HashTagLink;
|
import com.dbnt.kcgfilemanager.model.HashTagLink;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
public interface HashTagLinkRepository extends JpaRepository<HashTagLink, HashTagLink.HashTagLinkId> {
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface HashTagLinkRepository extends JpaRepository<HashTagLink, HashTagLink.HashTagLinkId> {
|
||||||
|
List<HashTagLink> findByContentSeq(int contentSeq);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class BoardService {
|
||||||
BoardLog log = new BoardLog();
|
BoardLog log = new BoardLog();
|
||||||
log.setContentSeq(contentSeq);
|
log.setContentSeq(contentSeq);
|
||||||
log.setLogSeq(lastLog == null?1:(lastLog.getLogSeq()+1));
|
log.setLogSeq(lastLog == null?1:(lastLog.getLogSeq()+1));
|
||||||
log.setLogStatus(status.getKey());
|
log.setLogStatus(status.name());
|
||||||
log.setDescription(description);
|
log.setDescription(description);
|
||||||
log.setCreateId(createId);
|
log.setCreateId(createId);
|
||||||
boardLogRepository.save(log);
|
boardLogRepository.save(log);
|
||||||
|
|
@ -126,7 +126,9 @@ public class BoardService {
|
||||||
@Transactional
|
@Transactional
|
||||||
public Board selectContentByContentSeqAndViewCntUp(Integer contentSeq) {
|
public Board selectContentByContentSeqAndViewCntUp(Integer contentSeq) {
|
||||||
Board target = boardRepository.findById(contentSeq).orElse(null);
|
Board target = boardRepository.findById(contentSeq).orElse(null);
|
||||||
|
if(target.getStatus()==null){
|
||||||
target.setViewCnt(target.getViewCnt()+1);
|
target.setViewCnt(target.getViewCnt()+1);
|
||||||
|
}
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +159,32 @@ public class BoardService {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Integer deleteContent(Board content, UserInfo user) {
|
||||||
|
int contentSeq = content.getContentSeq();
|
||||||
|
content = boardRepository.findById(contentSeq).orElse(null);
|
||||||
|
content.setTitle("삭제된 게시물");
|
||||||
|
content.setDescription(null);
|
||||||
|
content.setStatus("D");
|
||||||
|
saveBoardLog(contentSeq, LogStatus.DELETE, null, user.getUserId());
|
||||||
|
deleteHashTagLink(contentSeq);
|
||||||
|
deleteFileInfo(contentSeq);
|
||||||
|
return contentSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteHashTagLink(int contentSeq){
|
||||||
|
List<HashTagLink> tagLinkList = hashTagLinkRepository.findByContentSeq(contentSeq);
|
||||||
|
hashTagLinkRepository.deleteAll(tagLinkList);
|
||||||
|
}
|
||||||
|
private void deleteFileInfo(int contentSeq){
|
||||||
|
List<FileInfo> fileInfoList = fileInfoRepository.findByContentSeqOrderByFileSeqAsc(contentSeq);
|
||||||
|
for(FileInfo fileInfo: fileInfoList){
|
||||||
|
File file = new File(fileInfo.getSavePath(), fileInfo.getConversionName());
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
fileInfoRepository.deleteAll(fileInfoList);
|
||||||
|
}
|
||||||
|
|
||||||
private String makeFilePath(Integer categorySeq){
|
private String makeFilePath(Integer categorySeq){
|
||||||
BoardCategory category = boardCategoryRepository.findById(categorySeq).orElse(null);
|
BoardCategory category = boardCategoryRepository.findById(categorySeq).orElse(null);
|
||||||
if(category.getParentSeq()==null){
|
if(category.getParentSeq()==null){
|
||||||
|
|
@ -169,5 +197,4 @@ public class BoardService {
|
||||||
double unitSelector = Math.floor(Math.log(fileSize)/Math.log(1024));
|
double unitSelector = Math.floor(Math.log(fileSize)/Math.log(1024));
|
||||||
return Math.round((fileSize/Math.pow(1024, unitSelector))*100)/100d+" "+units[(int)unitSelector];
|
return Math.round((fileSize/Math.pow(1024, unitSelector))*100)/100d+" "+units[(int)unitSelector];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
FROM BOARD A
|
FROM BOARD A
|
||||||
INNER JOIN USER_INFO B
|
INNER JOIN USER_INFO B
|
||||||
ON A.CREATE_ID = B.USER_ID
|
ON A.CREATE_ID = B.USER_ID
|
||||||
INNER JOIN (SELECT CONTENT_SEQ, MAX(FILE_SEQ) AS FILE_CNT
|
LEFT OUTER JOIN (SELECT CONTENT_SEQ, MAX(FILE_SEQ) AS FILE_CNT
|
||||||
FROM FILE_INFO
|
FROM FILE_INFO
|
||||||
GROUP BY CONTENT_SEQ ) C
|
GROUP BY CONTENT_SEQ ) C
|
||||||
ON A.CONTENT_SEQ = C.CONTENT_SEQ
|
ON A.CONTENT_SEQ = C.CONTENT_SEQ
|
||||||
|
|
@ -30,11 +30,11 @@
|
||||||
FROM BOARD A
|
FROM BOARD A
|
||||||
INNER JOIN USER_INFO B
|
INNER JOIN USER_INFO B
|
||||||
ON A.CREATE_ID = B.USER_ID
|
ON A.CREATE_ID = B.USER_ID
|
||||||
INNER JOIN (SELECT CONTENT_SEQ, MAX(FILE_SEQ) AS FILE_CNT
|
LEFT OUTER JOIN (SELECT CONTENT_SEQ, MAX(FILE_SEQ) AS FILE_CNT
|
||||||
FROM FILE_INFO
|
FROM FILE_INFO
|
||||||
GROUP BY CONTENT_SEQ ) C
|
GROUP BY CONTENT_SEQ ) C
|
||||||
ON A.CONTENT_SEQ = C.CONTENT_SEQ
|
ON A.CONTENT_SEQ = C.CONTENT_SEQ
|
||||||
WHERE CATEGORY_SEQ = ${categorySeq}
|
WHERE A.CATEGORY_SEQ = ${categorySeq}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectHashTagListFromContentSeq" resultType="HashTag" parameterType="int">
|
<select id="selectHashTagListFromContentSeq" resultType="HashTag" parameterType="int">
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,28 @@ $(document).on('click', '#zipDownBtn', function (){
|
||||||
alert("선택된 파일이 없습니다.")
|
alert("선택된 파일이 없습니다.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
$(document).on('click', '#deleteBtn', function (){
|
||||||
|
if(confirm("이 게시물을 삭제하시겠습니까?\n되돌릴 수 없습니다.")){
|
||||||
|
$.ajax({
|
||||||
|
type : 'DELETE',
|
||||||
|
url : "/board/deleteContent",
|
||||||
|
data : {contentSeq: Number($(this).attr("data-contentseq"))},
|
||||||
|
beforeSend: function (xhr){
|
||||||
|
xhr.setRequestHeader($("[name='_csrf_header']").val(), $("[name='_csrf']").val());
|
||||||
|
},
|
||||||
|
success : function(data) {
|
||||||
|
alert("저장되었습니다.");
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error : function(xhr, status) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(document).on('click', '#modifyBtn', function (){
|
||||||
|
|
||||||
|
})
|
||||||
function getContentSeq(){
|
function getContentSeq(){
|
||||||
return $(".contentCheckBox:checked").val();
|
return $(".contentCheckBox:checked").val();
|
||||||
}
|
}
|
||||||
|
|
@ -65,8 +86,10 @@ function getBoardContent(contentSeq){
|
||||||
dataType:"html",
|
dataType:"html",
|
||||||
success: function(html){
|
success: function(html){
|
||||||
$("#contentDiv").empty().append(html)
|
$("#contentDiv").empty().append(html)
|
||||||
|
if($("#contentStatus").val() !== "D"){
|
||||||
const viewCntTd = $(".contentCheckBox:checked").parents("tr").find(".viewCntTd");
|
const viewCntTd = $(".contentCheckBox:checked").parents("tr").find(".viewCntTd");
|
||||||
viewCntTd.text(Number(viewCntTd.text())+1);
|
viewCntTd.text(Number(viewCntTd.text())+1);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error:function(){
|
error:function(){
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-sm-4"></div>
|
<div class="col-sm-4"></div>
|
||||||
<div class="col-sm-auto form-check ms-3">
|
<div class="col-sm-auto form-check ms-3">
|
||||||
<input type="hidden" name="userRole" value="ROLE_MEMBER">
|
<input type="hidden" name="userRole" value="ROLE_USER">
|
||||||
<input class="form-check-input" type="checkbox" value="ROLE_ADMIN" id="roleAdmin" name="userRole" th:checked="${#strings.contains(userInfo.userRole,'ADMIN')}">
|
<input class="form-check-input" type="checkbox" value="ROLE_ADMIN" id="roleAdmin" name="userRole" th:checked="${#strings.contains(userInfo.userRole,'ADMIN')}">
|
||||||
<label class="form-check-label" for="roleAdmin">관리자 계정</label>
|
<label class="form-check-label" for="roleAdmin">관리자 계정</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-sm-4"></div>
|
<div class="col-sm-4"></div>
|
||||||
<div class="col-sm-auto form-check ms-3">
|
<div class="col-sm-auto form-check ms-3">
|
||||||
<input type="hidden" name="userRole" value="ROLE_MEMBER">
|
<input type="hidden" name="userRole" value="ROLE_USER">
|
||||||
<input class="form-check-input" type="checkbox" value="ROLE_ADMIN" id="roleAdmin" name="userRole">
|
<input class="form-check-input" type="checkbox" value="ROLE_ADMIN" id="roleAdmin" name="userRole">
|
||||||
<label class="form-check-label" for="roleAdmin">관리자 계정</label>
|
<label class="form-check-label" for="roleAdmin">관리자 계정</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<input type="hidden" id="detailViewContentSeq" th:value="${content.contentSeq}">
|
<input type="hidden" id="detailViewContentSeq" th:value="${content.contentSeq}">
|
||||||
|
<input type="hidden" id="contentStatus" th:value="${content.status}">
|
||||||
<div class="row justify-content-between">
|
<div class="row justify-content-between">
|
||||||
<div class="col-auto"><h3 th:text="${content.title}"></h3></div>
|
<div class="col-auto"><h5 class="fw-bold" th:text="${content.title}"></h5></div>
|
||||||
<div class="col-auto" th:text="|조회수: ${content.viewCnt}|"></div>
|
<div class="col-auto" th:text="|조회수: ${content.viewCnt}|"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-between border-bottom pb-3">
|
<div class="row justify-content-between border-bottom pb-3">
|
||||||
|
|
@ -11,7 +12,7 @@
|
||||||
<div class="col-auto" th:text="${content.createId}"></div>
|
<div class="col-auto" th:text="${content.createId}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row border-bottom py-3">
|
<div class="row border-bottom py-3">
|
||||||
<div class="col-8">
|
<div th:class="|col-${content.childFileList.size()>1?8:12}|">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -31,7 +32,7 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4 my-auto">
|
<div class="col-4 m-auto" th:if="${content.childFileList.size()>1}">
|
||||||
<button type="button" class="btn btn-info m-1" id="zipDownBtn"><i class="bi bi-file-zip"></i> 선택된 파일<br>압축 다운로드</button>
|
<button type="button" class="btn btn-info m-1" id="zipDownBtn"><i class="bi bi-file-zip"></i> 선택된 파일<br>압축 다운로드</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -43,4 +44,14 @@
|
||||||
<div class="col-auto" th:text="'#'+${hashTag.tagName}"></div>
|
<div class="col-auto" th:text="'#'+${hashTag.tagName}"></div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
|
<th:block th:if="${content.createId == loginUser.userId || loginUser.userRole.contains('ADMIN')}">
|
||||||
|
<div class="row justify-content-between py-3">
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="btn btn-danger" id="deleteBtn" th:data-contentseq="${content.contentSeq}"><i class="bi bi-trash"></i> 삭제</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="btn btn-warning" id="modifyBtn" th:data-contentseq="${content.contentSeq}"><i class="bi bi-pencil-square"></i> 수정</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
</th:block>
|
</th:block>
|
||||||
<div layout:fragment="content">
|
<div layout:fragment="content">
|
||||||
<main class="pt-3">
|
<main class="pt-3">
|
||||||
|
<input type="hidden" name="_csrf_header" th:value="${_csrf.headerName}"/>
|
||||||
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
|
||||||
<h4 th:text="${pageTitle}"></h4>
|
<h4 th:text="${pageTitle}"></h4>
|
||||||
<div class="row mx-0">
|
<div class="row mx-0">
|
||||||
<div class="col-12 card">
|
<div class="col-12 card">
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
<form th:action="@{/signup}" method="post">
|
<form th:action="@{/signup}" method="post">
|
||||||
<input type="text" name="userId" placeholder="이메일 입력해주세요" />
|
<input type="text" name="userId" placeholder="이메일 입력해주세요" />
|
||||||
<input type="password" name="password" placeholder="비밀번호" />
|
<input type="password" name="password" placeholder="비밀번호" />
|
||||||
<input type="radio" name="userRole" value="ROLE_ADMIN,ROLE_MEMBER" /> admin
|
<input type="radio" name="userRole" value="ROLE_ADMIN,ROLE_USER" /> admin
|
||||||
<input type="radio" name="userRole" value="ROLE_MEMBER" checked="checked" />
|
<input type="radio" name="userRole" value="ROLE_USER" checked="checked" />
|
||||||
member <br />
|
member <br />
|
||||||
<button type="submit">가입하기</button>
|
<button type="submit">가입하기</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue