수정전 StudyController와 StudyService 리팩토링
package me.weekbelt.studyolle.study;
@RequiredArgsConstructor
@Controller
public class StudyController {
@GetMapping("/study/{path}")
public String viewStudy(@CurrentAccount Account account, @PathVariable String path,
Model model) {
Study study = studyService.getStudy(path); // 추가
model.addAttribute(account);
model.addAttribute(study); // 수정
return "study/view";
}
@GetMapping("/study/{path}/members")
public String viewStudyMember(@CurrentAccount Account account,
@PathVariable String path, Model model) {
Study study = studyService.getStudy(path); // 추가
model.addAttribute(account);
model.addAttribute(study); // 수정
return "study/members";
}
}
package me.weekbelt.studyolle.study;
@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {
// ......
public Study getStudy(String path) {
Study study = this.studyRepository.findByPath(path);
if (study == null) {
throw new IllegalArgumentException(path + "에 해당하는 스터디가 없습니다.");
}
return study;
}
}
AccountController, AccountService 리팩토링
package me.weekbelt.studyolle.account;
@Slf4j
@Transactional
@RequiredArgsConstructor
@Service
public class AccountService implements UserDetailsService {
// .........
public Account getAccount(String nickname) {
Account account = accountRepository.findByNickname(nickname);
if (account == null){
throw new IllegalArgumentException(nickname + "에 해당하는 사용자가 없습니다.");
}
return account;
}
}
package me.weekbelt.studyolle.account;
@RequiredArgsConstructor
@Controller
public class AccountController {
// ............
@GetMapping("/profile/{nickname}")
public String viewProfile(@PathVariable String nickname, Model model,
@CurrentAccount Account account){
Account accountToView = accountService.getAccount(nickname);
model.addAttribute(accountToView); // byNickname의 상위타입의 이름의 camel_case로 key값이 설정된다.
model.addAttribute("isOwner", accountToView.equals(account));
return "account/profile";
}
// .........
}
HtmlEmailService 리팩토링
package me.weekbelt.studyolle.mail;
@Slf4j
@RequiredArgsConstructor
@Profile("dev")
@Component
public class HtmlEmailService implements EmailService{
private final JavaMailSender javaMailSender;
@Override
public void sendEmail(EmailMessage emailMessage) {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
try {
// .......
} catch (MessagingException e) {
// .......
throw new RuntimeException(e); // 추가
}
}
}
- 스터디 매니저만 스터디 설정 기능을 사용할 수 있다.
스터디 소개 수정 뷰 요청을 처리할 핸들러 생성
package me.weekbelt.studyolle.study;
@RequiredArgsConstructor
@RequestMapping("/study/{path}/settings")
@Controller
public class StudySettingsController {
private final StudyService studyService;
private final ModelMapper modelMapper;
@GetMapping("/description")
public String viewStudySetting(@CurrentAccount Account account,
@PathVariable String path, Model model) {
Study study = studyService.getStudyToUpdate(account, path);
model.addAttribute(account);
model.addAttribute(study);
model.addAttribute(modelMapper.map(study, StudyDescriptionForm.class));
return "study/settings/description";
}
}
StudyService에서 수정할 스터디를 가져오게 하는 getStudyToUpdate 메소드 생성
package me.weekbelt.studyolle.study;
@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {
// ..........
public Study getStudyToUpdate(Account account, String path) {
Study study = this.getStudy(path);
if (!account.isManagerOf(study)) {
throw new AccessDeniedException("해당 기능을 사용할 수 없습니다.");
}
return study;
}
// ..........
}
getStudyToUpdate로 스터디를 가져올때 수정 하려면 매니저의 권한이 있어야 한다. 따라서 Account 엔티티에 해당 스터디에 대한 권한이 있는지 확인하는 isManagerOf메소드를 추가한다.
package me.weekbelt.studyolle.domain;
@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
@Entity
public class Account {
// ........
public boolean isManagerOf(Study study) {
return study.getManagers().contains(this);
}
}
스터디 소개 수정 뷰 작성
스터디 소개 수정 요청시 데이터를 전송할 StudyDescriptionForm DTO객체 생성
package me.weekbelt.studyolle.study.form;
@NoArgsConstructor
@Data
public class StudyDescriptionForm {
@NotBlank
@Length(max = 100)
private String shortDescription;
@NotBlank
private String fullDescription;
}
스터디 소개 수정 처리 핸들러 작성
package me.weekbelt.studyolle.study;
@RequiredArgsConstructor
@RequestMapping("/study/{path}/settings")
@Controller
public class StudySettingsController {
// ..........
@PostMapping("/description")
public String updateStudyInfo(@CurrentAccount Account account,
@PathVariable String path,
@Valid StudyDescriptionForm studyDescriptionForm,
Errors errors, Model model,
RedirectAttributes attributes) {
Study study = studyService.getStudyToUpdate(account, path);
if (errors.hasErrors()) {
model.addAttribute(account);
model.addAttribute(study);
return "study/settings/description";
}
studyService.updateStudyDescription(study, studyDescriptionForm);
attributes.addFlashAttribute("message", "스터디 소개를 수정했습니다.");
return "redirect:/study/" + getPath(path) + "/settings/description";
}
private String getPath(String path) {
return URLEncoder.encode(path, StandardCharsets.UTF_8);
}
}
스터디 소개 수정 처리하는 updateStudyDescription메소드 작성
package me.weekbelt.studyolle.study;
@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {
// ......
private final ModelMapper modelMapper;
// ......
public void updateStudyDescription(Study study, StudyDescriptionForm studyDescriptionForm) {
modelMapper.map(studyDescriptionForm, study);
}
}
fragments.html에 fragment들 추가
<div th:fragment="study-settings-menu (currentMenu)" class="row px-3 justify-content-center">
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'description'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/description'}">소개</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'image'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/banner'}">배너 이미지</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'tags'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/tags'}">스터디 주제</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'zones'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/zones'}">활동 지역</a>
<a class="list-group-item list-group-item-action list-group-item-danger" th:classappend="${currentMenu == 'study'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/study'}">스터디</a>
</div>
<div th:fragment="editor-script">
<script src="/node_modules/summernote/dist/summernote-bs4.js"></script>
<script type="application/javascript">
$(function () {
$('#fullDescription').summernote({
fontNames: ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Noto Sans KR', 'Merriweather'],
placeholder: '스터디의 목표, 일정, 진행 방식, 사용할 교재 또는 인터넷 강좌 그리고 모집중인 스터디원 등 스터디에 대해 자세히 적어 주세요.',
tabsize: 2,
height: 300
});
});
</script>
</div>
<div th:fragment="message" th:if="${message}" class="alert alert-info alert-dismissible fade show mt-3" role="alert">
<span th:text="${message}">완료</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<script th:fragment="tooltip" type="application/javascript">
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
study/settings/description.html 생성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html::head"></head>
<body class="bg-light">
<div th:replace="fragments.html::main-nav"></div>
<div th:replace="fragments.html::study-banner"></div>
<div class="container">
<div th:replace="fragments.html::study-info"></div>
<div th:replace="fragments.html::study-menu(studyMenu='settings')"></div>
<div class="row mt-3 justify-content-center">
<div class="col-2">
<div th:replace="fragments.html::study-settings-menu(currentMenu='description')"></div>
</div>
<div class="col-8">
<div th:replace="fragments.html::message"></div>
<form class="needs-validation" th:action="@{'/study/' + ${study.getPath()} + '/settings/description'}"
th:object="${studyDescriptionForm}" method="post" novalidate>
<div class="form-group">
<label for="shortDescription">짧은 소개</label>
<textarea id="shortDescription" type="textarea" th:field="*{shortDescription}"
class="form-control" placeholder="스터디를 짧게 소개해 주세요."
aria-describedby="shortDescriptionHelp"
required maxlength="100"></textarea>
<small id="shortDescriptionHelp" class="form-text text-muted">
100자 이내로 스터디를 짧은 소개해 주세요.
</small>
<small class="invalid-feedback">짧은 소개를 입력하세요.</small>
<small class="form-text text-danger" th:if="${#fields.hasErrors('shortDescription')}"
th:errors="*{shortDescription}"></small>
</div>
<div class="form-group">
<label for="fullDescription">상세 소개</label>
<textarea id="fullDescription" type="textarea" th:field="*{fullDescription}"
class="form-control" placeholder="스터디를 짧게 소개해 주세요."
aria-describedby="fullDescriptionHelp"
required maxlength="100"></textarea>
<small id="fullDescriptionHelp" class="form-text text-muted">
스터디의 목표, 일정, 진행 방식, 사용할 교재 또는 인터넷 강좌 그리고 모집중인 스터디원 등 스터디에 대해 자세히 적어 주세요.
</small>
<small class="invalid-feedback">상세 소개를 입력하세요.</small>
<small class="form-text text-danger" th:if="${#fields.hasErrors('fullDescription')}"
th:errors="*{fullDescription}"></small>
</div>
<div class="form-group">
<button class="btn btn-primary btn-block" type="submit" aria-describedby="submitHelp">
수정 하기
</button>
</div>
</form>
</div>
</div>
<!-- footer -->
<div th:replace="fragments.html::footer"></div>
</div>
<script th:replace="fragments.html::form-validation"></script>
<script th:replace="fragments.html::editor-script"></script>
</body>
</html>
study/form.html 수정
// ..............
<!-- footer -->
<div th:replace="fragments.html::footer"></div>
</div>
<script th:replace="fragments.html::form-validation"></script>
<script th:replace="fragments.html::editor-script"></script> // 추가
</body>
</html>
참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#
'스프링과 JPA 기반 웹 어플리케이션 개발 > 4부 스터디' 카테고리의 다른 글
54. 스터디 설정 - 태그/지역 (0) | 2020.04.24 |
---|---|
53. 스터디 설정 - 배너 (0) | 2020.04.24 |
51. 스터디 폼 & 개설 & 멤버 조회 테스트 (0) | 2020.04.24 |
50. 스터디 구성원 조회 (0) | 2020.04.23 |
49. 스터디 조회 (0) | 2020.04.23 |