본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/4부 스터디

52. 스터디 설정 - 소개 수정

by Backchus 2020. 4. 24.

수정전 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">&times;</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 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습니다. 이 강좌를 충분히 학습한다면 여러분 만의 웹 서비스를 만들거나 취직에 도움이 될만한 포트폴리오를 만들 수 있을 겁니다. 활용 웹 개발 프레임워크 및 라이브러리 Java Spring Spring Boot Spring Data JPA Thymeleaf 온라인 강의 스

www.inflearn.com