Skip to content

Commit

Permalink
[BE] feat: 어드민 페이지 추가 관리 기능 추가, 개선 (#459) (#460)
Browse files Browse the repository at this point in the history
* feat: AdminController View Controller 분리

* feat: 축제, 학교 관리자 페이지 추가

* feat: 학교 CUD 기능 추가

* refactor: 관리자 View, API URL 분리

* refactor: 관리자 View URL 경로 수정

* refactor: FestivalService clock 의존성 추가

* refactor: 검증 로직 외부로 추출, 일관화

* refactor: 어드민 페이지 리팩터링

* feat: 축제 삭제, 수정 기능 추가

* fix: FestivalServiceTest Clock 의존성 추가

* refactor: Validator 리팩터링, Javadoc 추가

* refactor: javascript type module로 변경

- 추후 js 파일에서 import하기 위함

* refactor: Detail 페이지 PathVariable으로 받도록 변경

* feat: 공연 CRUD 기능 추가

* fix: 에러 코드 수정

* refactor: 학교 상세 페이지 삭제 에러 시 모달 창 닫기도록 변경

* feat: 어드민 페이지 티켓 생성, 조회 기능 추가

* refactor: 예외 메시지 명확하게 변경

* refactor: 어드민 페이지 폼, 함수명 명확하게 변경

* refactor: 어드민 페이지 개선

* feat: 학교 생성, 수정 시 Validation 추가

* fix: 446번 이슈 충돌 수정

* feat: RequestDto에 검증 추가

* chore: 코드 줄바꿈으로 가독성 향상

* feat: 축제 응답에 학교 ID 추가

* fix: 테스트 코드 수정

* fix: 어드민 로그인, 가입 페이지 관리자에서 어드민으로 변경

* fix: 축제 생성 폼 id 변경

* fix: 클라이언트 날짜 검증 로직 제거
- 바닐라 js로 구현이 매우 힘듬..

* fix: 머지 충돌 해결
  • Loading branch information
seokjin8678 authored Oct 6, 2023
1 parent 6022378 commit 820a6c0
Show file tree
Hide file tree
Showing 50 changed files with 2,378 additions and 541 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAuthInterceptor())
.addPathPatterns("/admin/**", "/js/admin/**")
.excludePathPatterns("/admin/login", "/admin/initialize");
.excludePathPatterns("/admin/login", "/admin/api/login", "/admin/api/initialize");
registry.addInterceptor(memberAuthInterceptor())
.addPathPatterns("/member-tickets/**", "/members/**", "/auth/**", "/students/**")
.excludePathPatterns("/auth/oauth2");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ public enum ErrorCode {
NOT_ENTRY_TIME("입장 가능한 시간이 아닙니다."),
EXPIRED_ENTRY_CODE("만료된 입장 코드입니다."),
INVALID_ENTRY_CODE("올바르지 않은 입장코드입니다."),
INVALID_TICKET_OPEN_TIME("티켓은 공연 시작 전에 오픈되어야 합니다."),
INVALID_TICKET_OPEN_TIME("티켓 오픈 시간은 공연 시작 이전 이어야 합니다."),
INVALID_STAGE_START_TIME("공연은 축제 기간 중에만 진행될 수 있습니다."),
INVALID_MIN_TICKET_AMOUNT("티켓은 적어도 한장 이상 발급해야합니다."),
LATE_TICKET_ENTRY_TIME("입장 시간은 공연 시간보다 빨라야합니다."),
EARLY_TICKET_ENTRY_TIME("입장 시간은 공연 시작 12시간 이내여야 합니다."),
EARLY_TICKET_ENTRY_THAN_OPEN("입장 시간은 티켓 오픈 시간 이후여야합니다."),
TICKET_SOLD_OUT("매진된 티켓입니다."),
INVALID_FESTIVAL_START_DATE("축제 시작 일자는 과거일 수 없습니다."),
INVALID_FESTIVAL_DURATION("축제 시작 일자는 종료일자 이전이어야합니다."),
INVALID_TICKET_CREATE_TIME("티켓 예매 시작 후 새롭게 티켓을 발급할 수 없습니다."),
INVALID_FESTIVAL_DURATION("축제 시작 일은 종료일 이전이어야 합니다."),
INVALID_FESTIVAL_START_DATE("축제 시작 일은 과거일 수 없습니다."),
INVALID_TICKET_CREATE_TIME("티켓 오픈 시간 이후 새롭게 티켓을 발급할 수 없습니다."),
OAUTH2_NOT_SUPPORTED_SOCIAL_TYPE("해당 OAuth2 제공자는 지원되지 않습니다."),
RESERVE_TICKET_OVER_AMOUNT("예매 가능한 수량을 초과했습니다."),
NEED_STUDENT_VERIFICATION("학생 인증이 필요합니다."),
Expand All @@ -25,6 +25,10 @@ public enum ErrorCode {
DUPLICATE_STUDENT_EMAIL("이미 인증된 이메일입니다."),
TICKET_CANNOT_RESERVE_STAGE_START("공연의 시작 시간 이후로 예매할 수 없습니다."),
INVALID_STUDENT_VERIFICATION_CODE("올바르지 않은 학생 인증 코드입니다."),
DELETE_CONSTRAINT_FESTIVAL("공연이 등록된 축제는 삭제할 수 없습니다."),
DELETE_CONSTRAINT_STAGE("티켓이 등록된 공연은 삭제할 수 없습니다."),
DELETE_CONSTRAINT_SCHOOL("학생 또는 축제에 등록된 학교는 삭제할 수 없습니다."),
DUPLICATE_SCHOOL("이미 존재하는 학교 정보입니다."),


// 401
Expand All @@ -34,7 +38,6 @@ public enum ErrorCode {
NEED_AUTH_TOKEN("로그인이 필요한 서비스입니다."),
INCORRECT_PASSWORD_OR_ACCOUNT("비밀번호가 틀렸거나, 해당 계정이 없습니다."),
DUPLICATE_ACCOUNT_USERNAME("해당 계정이 존재합니다."),
DUPLICATE_SCHOOL("이미 존재하는 학교 정보입니다."),

// 403
NOT_ENOUGH_PERMISSION("해당 권한이 없습니다."),
Expand Down
49 changes: 49 additions & 0 deletions backend/src/main/java/com/festago/common/util/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.festago.common.util;

public final class Validator {

private Validator() {
}

/**
* 문자열의 최대 길이를 검증합니다. null 값은 무시됩니다. 최대 길이가 0 이하이면 예외를 던집니다. 문자열의 길이가 maxLength보다 작거나 같으면 예외를 던지지 않습니다.
*
* @param input 검증할 문자열
* @param maxLength 검증할 문자열의 최대 길이
* @param message 예외 메시지
* @throws IllegalArgumentException 문자열의 길이가 초과되거나, 최대 길이가 0 이하이면
*/
public static void maxLength(CharSequence input, int maxLength, String message) {
if (maxLength <= 0) {
throw new IllegalArgumentException("검증 길이는 0보다 커야합니다.");
}
// avoid NPE
if (input == null) {
return;
}
if (input.length() > maxLength) {
throw new IllegalArgumentException(message);
}
}

/**
* 문자열의 최소 길이를 검증합니다. null 값은 무시됩니다. 최소 길이가 0 이하이면 예외를 던집니다. 문자열의 길이가 minLength보다 크거나 같으면 예외를 던지지 않습니다.
*
* @param input 검증할 문자열
* @param minLength 검증할 문자열의 최소 길이
* @param message 예외 메시지
* @throws IllegalArgumentException 문자열의 길이가 작으면, 최대 길이가 0 이하이면
*/
public static void minLength(CharSequence input, int minLength, String message) {
if (minLength <= 0) {
throw new IllegalArgumentException("검증 길이는 0보다 커야합니다.");
}
// avoid NPE
if (input == null) {
return;
}
if (input.length() < minLength) {
throw new IllegalArgumentException(message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@
import com.festago.festival.dto.FestivalCreateRequest;
import com.festago.festival.dto.FestivalDetailResponse;
import com.festago.festival.dto.FestivalResponse;
import com.festago.festival.dto.FestivalUpdateRequest;
import com.festago.festival.dto.FestivalsResponse;
import com.festago.festival.repository.FestivalRepository;
import com.festago.school.domain.School;
import com.festago.school.repository.SchoolRepository;
import com.festago.stage.domain.Stage;
import com.festago.stage.repository.StageRepository;
import java.time.Clock;
import java.time.LocalDate;
import java.util.List;
import org.springframework.dao.DataIntegrityViolationException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -29,18 +32,18 @@ public class FestivalService {
private final FestivalRepository festivalRepository;
private final StageRepository stageRepository;
private final SchoolRepository schoolRepository;
private final Clock clock;

public FestivalResponse create(FestivalCreateRequest request) {
School school = schoolRepository.findById(request.schoolId())
.orElseThrow(() -> new NotFoundException(ErrorCode.SCHOOL_NOT_FOUND));
Festival festival = request.toEntity(school);
validate(festival);
Festival newFestival = festivalRepository.save(festival);
return FestivalResponse.from(newFestival);
return FestivalResponse.from(festivalRepository.save(festival));
}

private void validate(Festival festival) {
if (!festival.canCreate(LocalDate.now())) {
if (!festival.canCreate(LocalDate.now(clock))) {
throw new BadRequestException(ErrorCode.INVALID_FESTIVAL_START_DATE);
}
}
Expand All @@ -53,11 +56,32 @@ public FestivalsResponse findAll() {

@Transactional(readOnly = true)
public FestivalDetailResponse findDetail(Long festivalId) {
Festival festival = festivalRepository.findById(festivalId)
.orElseThrow(() -> new NotFoundException(ErrorCode.FESTIVAL_NOT_FOUND));
Festival festival = findFestival(festivalId);
List<Stage> stages = stageRepository.findAllDetailByFestivalId(festivalId).stream()
.sorted(comparing(Stage::getStartTime))
.toList();
return FestivalDetailResponse.of(festival, stages);
}

private Festival findFestival(Long festivalId) {
return festivalRepository.findById(festivalId)
.orElseThrow(() -> new NotFoundException(ErrorCode.FESTIVAL_NOT_FOUND));
}

public void update(Long festivalId, FestivalUpdateRequest request) {
Festival festival = findFestival(festivalId);
festival.changeName(request.name());
festival.changeThumbnail(request.thumbnail());
festival.changeDate(request.startDate(), request.endDate());
validate(festival);
}

public void delete(Long festivalId) {
try {
festivalRepository.deleteById(festivalId);
festivalRepository.flush();
} catch (DataIntegrityViolationException e) {
throw new BadRequestException(ErrorCode.DELETE_CONSTRAINT_FESTIVAL);
}
}
}
53 changes: 29 additions & 24 deletions backend/src/main/java/com/festago/festival/domain/Festival.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.festago.festival.domain;

import com.festago.common.domain.BaseTimeEntity;
import com.festago.common.exception.BadRequestException;
import com.festago.common.exception.ErrorCode;
import com.festago.common.util.Validator;
import com.festago.school.domain.School;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand All @@ -14,6 +13,7 @@
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.time.LocalDateTime;
import org.springframework.util.Assert;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

Expand Down Expand Up @@ -64,37 +64,26 @@ public Festival(Long id, String name, LocalDate startDate, LocalDate endDate, St
}

private void validate(String name, LocalDate startDate, LocalDate endDate, String thumbnail) {
checkNotNull(name, startDate, endDate, thumbnail);
checkLength(name, thumbnail);
validateName(name);
validateThumbnail(thumbnail);
validateDate(startDate, endDate);
}

private void checkNotNull(String name, LocalDate startDate, LocalDate endDate, String thumbnail) {
if (name == null ||
startDate == null ||
endDate == null ||
thumbnail == null) {
throw new IllegalArgumentException("Festival 은 허용되지 않은 null 값으로 생성할 수 없습니다.");
}
}

private void checkLength(String name, String thumbnail) {
if (overLength(name, 50) ||
overLength(thumbnail, 255)) {
throw new IllegalArgumentException("Festival 의 필드로 허용된 길이를 넘은 column 을 넣을 수 없습니다.");
}
private void validateName(String name) {
Assert.notNull(name, "name은 null 값이 될 수 없습니다.");
Validator.maxLength(name, 50, "name은 50글자를 넘을 수 없습니다.");
}

private boolean overLength(String target, int maxLength) {
if (target == null) {
return false;
}
return target.length() > maxLength;
private void validateThumbnail(String thumbnail) {
Assert.notNull(thumbnail, "thumbnail은 null 값이 될 수 없습니다.");
Validator.maxLength(thumbnail, 255, "thumbnail은 50글자를 넘을 수 없습니다.");
}

private void validateDate(LocalDate startDate, LocalDate endDate) {
Assert.notNull(startDate, "startDate는 null 값이 될 수 없습니다.");
Assert.notNull(endDate, "endDate는 null 값이 될 수 없습니다.");
if (startDate.isAfter(endDate)) {
throw new BadRequestException(ErrorCode.INVALID_FESTIVAL_DURATION);
throw new IllegalArgumentException("축제 시작 일은 종료일 이전이어야 합니다.");
}
}

Expand All @@ -107,6 +96,22 @@ public boolean isNotInDuration(LocalDateTime time) {
return date.isBefore(startDate) || date.isAfter(endDate);
}

public void changeName(String name) {
validateName(name);
this.name = name;
}

public void changeThumbnail(String thumbnail) {
validateThumbnail(thumbnail);
this.thumbnail = thumbnail;
}

public void changeDate(LocalDate startDate, LocalDate endDate) {
validateDate(startDate, endDate);
this.startDate = startDate;
this.endDate = endDate;
}

public Long getId() {
return id;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

public record FestivalDetailResponse(
Long id,
Long schoolId,
String name,
LocalDate startDate,
LocalDate endDate,
Expand All @@ -19,6 +20,7 @@ public static FestivalDetailResponse of(Festival festival, List<Stage> stages) {
.toList();
return new FestivalDetailResponse(
festival.getId(),
festival.getSchool().getId(),
festival.getName(),
festival.getStartDate(),
festival.getEndDate(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

public record FestivalResponse(
Long id,
Long schoolId,
String name,
LocalDate startDate,
LocalDate endDate,
Expand All @@ -13,6 +14,7 @@ public record FestivalResponse(
public static FestivalResponse from(Festival festival) {
return new FestivalResponse(
festival.getId(),
festival.getSchool().getId(),
festival.getName(),
festival.getStartDate(),
festival.getEndDate(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.festago.festival.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;

public record FestivalUpdateRequest(
@NotBlank(message = "name은 공백일 수 없습니다.") String name,
@NotNull(message = "startDate는 null일 수 없습니다.") @DateTimeFormat(iso = ISO.DATE) LocalDate startDate,
@NotNull(message = "endDate는 null일 수 없습니다.") @DateTimeFormat(iso = ISO.DATE) LocalDate endDate,
String thumbnail
) {

}
Loading

0 comments on commit 820a6c0

Please sign in to comment.