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

48. 스터디 개설

by Backchus 2020. 4. 23.

fragments.html 에서 스터디 개설 url 수정

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

// ..............

    <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item">
                <form th:action="@{/search/study}" class="form-inline" method="get">
                    <input class="form-control mr-sm-2" name="keyword" type="search" placeholder="스터디 찾기"
                           aria-label="Search"/>
                </form>
            </li>
        </ul>

        <ul class="navbar-nav justify-content-end">
            <li class="nav-item" sec:authorize="!isAuthenticated()">
                <a class="nav-link" th:href="@{/login}">로그인</a>
            </li>
            <li class="nav-item" sec:authorize="!isAuthenticated()">
                <a class="nav-link" th:href="@{/sign-up}">가입</a>
            </li>
            <li class="nav-item" sec:authorize="isAuthenticated()">
                <a class="nav-link" th:href="@{/notifications}">
                    <i class="fa fa-bell-o" aria-hidden="true"></i>
                </a>
            </li>
            <li class="nav-item" sec:authorize="isAuthenticated()">
                <a class="nav-link btn btn-outline-primary" th:href="@{/new-study}">   // /new-study로 수정
                    <i class="fa fa-plus" aria-hidden="true"></i>스터디 개설
                </a>
            </li>
            
            // ...........
</html>

 

스터디 폼 요청 처리와 스터디 폼

스터디 생성 폼의 요청을 받는 핸들러 작성

package me.weekbelt.studyolle.study;

@Controller
public class StudyController {

    @GetMapping("/new-study")
    public String newStudyForm(@CurrentAccount Account account, Model model) {
        model.addAttribute(account);
        model.addAttribute(new StudyForm());
        return "study/form";
    }
}

 

스터디 생성시 핸들러로 전달할 StudyForm DTO 생성

package me.weekbelt.studyolle.study.form;

@Data
public class StudyForm {

    @NotBlank
    @Length(min = 2, max = 20)
    @Pattern(regexp = "^[ㄱ-ㅎ가-힣a-z0-9_-]{2,20}$")
    private String path;

    @NotBlank
    @Length(max = 50)
    private String title;

    @NotBlank
    @Length(max = 100)
    private String shortDescription;

    @NotBlank
    private String fullDescription;
}

 

스터디 생성 화면 폼 생성(study/form)

스터디 상세 소개시 에디터 사용

 

Summernote - Super Simple WYSIWYG editor

Super Simple WYSIWYG Editor on Bootstrap Summernote is a JavaScript library that helps you create WYSIWYG editors online.

summernote.org

  • 부트스트랩과 연동이 편리함.
  • 한국 개발자들의 오픈 소스.
  • npm install summernote

 

fragments.html에 summernote 와 구글 폰트인 noto-sans link추가

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<head th:fragment="head">
    <meta charset="UTF-8">
    <title>StudyOlle</title>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css"/>
    <link rel="stylesheet" href="/node_modules/font-awesome/css/font-awesome.min.css">
    <link rel="stylesheet" href="/node_modules/@yaireo/tagify/dist/tagify.css">
    <link rel="stylesheet" href="/node_modules/summernote/dist/summernote-bs4.min.css">  // 추가
    <script src="/node_modules/jdenticon/dist/jdenticon.min.js"></script>
    <script src="/node_modules/jquery/dist/jquery.min.js"></script>
    <script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>

// ................

 

account/form.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 class="container">
    <div class="py-5 text-center">
        <h2>스터디 개설</h2>
    </div>
    <div class="row justify-content-center">
        <form class="needs-validation col-sm-10" th:action="@{/new-study}" method="post" th:object="${studyForm}" novalidate>
            <div class="form-group">
                <label for="path">스터디 URL</label>
                <input id="path" type="text" th:field="*{path}" class="form-control"
                    placeholder="예) study-path" aria-describedby="pathHelp" required min="2" max="20">
                <small id="pathHelp" class="form-text text-muted">
                    공백 없이 문자, 숫자, 대시(-)와 언더바(_)만 2자 이상 20자 이내로 입력하세요.
                    스터디 홈 주소에 사용합니다. 예) /study/<b>study-path</b>
                </small>
                <small class="invalid-feedback">스터디 경로를 입력하세요.</small>
                <small class="form-text text-danger" th:if="${#fields.hasErrors('path')}" th:errors="*{path}">Path Error</small>
            </div>
            <div class="form-group">
                <label for="title">스터디 이름</label>
                <input id="title" type="text" th:field="*{title}" class="form-control"
                       placeholder="스터디 이름" aria-describedby="titleHelp" required max="50">
                <small id="titleHelp" class="form-text text-muted">
                    스터디 이름을 50자 이내로 입력하세요.
                </small>
                <small class="invalid-feedback">스터디 경로를 입력하세요.</small>
                <small class="form-text text-danger" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Title Error</small>
            </div>
            <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">
                    55자 이내로 스터디를 짧게 소개해 주세요.
                </small>
                <small class="invalid-feedback">짧은 소개를 입력하세요.</small>
                <small class="form-text text-danger" th:if="${#fields.hasErrors('shortDescription')}" th:errors="*{shortDescription}">ShortDescription Error</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></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}">FullDescription Error</small>
            </div>

            <div class="form-group">
                <button class="btn btn-primary btn-block" type="submit" aria-describedby="submitHelp">
                    스터디 만들기기
               </button>
            </div>
        </form>
    </div>
    <!-- footer -->
    <div th:replace="fragments.html::footer"></div>
</div>
<script th:replace="fragments.html :: form-validation"></script>
</body>
</html>

 

study/form.html에 summernote를 추가하기위한 js 로직 추가

<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>

 

 

 

스터디개설

StudyRepository 생성

package me.weekbelt.studyolle.study;

@Transactional(readOnly = true)
public interface StudyRepository extends JpaRepository<Study, Long> {
    boolean existsByPath(String path);  
}

 

Study엔티티에 매니저를 추가해주는 메소드 추가

package me.weekbelt.studyolle.domain;

@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
@Entity
public class Study {

    // ........

    public void addManager(Account account) {
        this.managers.add(account);
    }
}

 

StudyService 생성

package me.weekbelt.studyolle.study;

@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {

    private final StudyRepository studyRepository;

    public Study createNewStudy(Study study, Account account) {
        Study newStudy = studyRepository.save(study);
        newStudy.addManager(account);
        return newStudy;
    }
}

 

스터디 작성 요청을 받는 핸들러 생성

package me.weekbelt.studyolle.study;

@RequiredArgsConstructor
@Controller
public class StudyController {

    private final StudyService studyService;
    private final ModelMapper modelMapper;

    // ...........

    @PostMapping("/new-study")
    public String newStudySubmit(@CurrentAccount Account account,
                                 @Valid StudyForm studyForm, Errors errors, Model model){
        if (errors.hasErrors()) {
            model.addAttribute(account);
            return "study/form";
        }

        Study newStudy = studyService.createNewStudy(modelMapper.map(studyForm, Study.class), account);
        return "redirect:/study/" + URLEncoder.encode(newStudy.getPath(), StandardCharsets.UTF_8);
    }
}

 

스터디 작성시 스터디 URL 중복 검증을 위한 커스텀 Validator 작성

package me.weekbelt.studyolle.study.validator;

@RequiredArgsConstructor
@Component
public class StudyFormValidator implements Validator {

    private  final StudyRepository studyRepository;

    @Override
    public boolean supports(Class<?> aClass) {
        return StudyForm.class.isAssignableFrom(aClass);
    }

    @Override
    public void validate(Object target, Errors errors) {
        StudyForm studyForm = (StudyForm) target;
        if (studyRepository.existsByPath(studyForm.getPath())){
            errors.rejectValue("path", "wrong.path", "스터디 경로값을 사용할 수 없습니다.");
        }
    }
}

 

StudyController에 StudyFormValidator를 적용하기 위한 설정 추가

package me.weekbelt.studyolle.study;

@RequiredArgsConstructor
@Controller
public class StudyController {

    // .......
    private final StudyFormValidator studyFormValidator;

    @InitBinder("studyForm")
    public void studyFormInitBinder(WebDataBinder webDataBinder){
        webDataBinder.addValidators(studyFormValidator);
    }

    // .......
}

 

 

참고: 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