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)
스터디 상세 소개시 에디터 사용
- 부트스트랩과 연동이 편리함.
- 한국 개발자들의 오픈 소스.
- 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 기반 웹 어플리케이션 개발 > 4부 스터디' 카테고리의 다른 글
51. 스터디 폼 & 개설 & 멤버 조회 테스트 (0) | 2020.04.24 |
---|---|
50. 스터디 구성원 조회 (0) | 2020.04.23 |
49. 스터디 조회 (0) | 2020.04.23 |
47. 스터디 도메인 (0) | 2020.04.23 |
46. 스터디 관리 기능 미리보기 (0) | 2020.04.23 |