Skip to content

Commit

Permalink
[BSVR-99] 경기장 내 구역 정보 생성 (#35)
Browse files Browse the repository at this point in the history
* feat: 구역 생성 컨트롤러

* feat: 요청에 중복 이름, 별칭이 있는지 확인

* feat: custom exception 추가

* fix: alias null 체크 추가

* fix: 오타 수정

* test: 테스트 코드 추가
  • Loading branch information
EunjiShin authored Jul 17, 2024
1 parent cc74697 commit a674d24
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
package org.depromeet.spot.application.common.dto;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;

import org.depromeet.spot.domain.common.RgbCode;

public record RgbCodeRequest(Integer red, Integer green, Integer blue) {
import io.swagger.v3.oas.annotations.Parameter;

public record RgbCodeRequest(
@Parameter(description = "rgb 코드 값:: red")
@Max(value = 255, message = "rgb 값은 0~255 만 입력 가능합니다.")
@Min(value = 0, message = "rgb 값은 0~255 만 입력 가능합니다.")
Integer red,
@Parameter(description = "rgb 코드 값:: green")
@Max(value = 255, message = "rgb 값은 0~255 만 입력 가능합니다.")
@Min(value = 0, message = "rgb 값은 0~255 만 입력 가능합니다.")
Integer green,
@Parameter(description = "rgb 코드 값:: blue")
@Max(value = 255, message = "rgb 값은 0~255 만 입력 가능합니다.")
@Min(value = 0, message = "rgb 값은 0~255 만 입력 가능합니다.")
Integer blue) {
public RgbCode toDomain() {
return RgbCode.builder().red(red).green(green).blue(blue).build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.depromeet.spot.application.section;

import java.util.List;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

import org.depromeet.spot.application.section.dto.request.CreateSectionRequest;
import org.depromeet.spot.usecase.port.in.section.CreateSectionUsecase;
import org.depromeet.spot.usecase.port.in.section.CreateSectionUsecase.CreateSectionCommand;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
@Tag(name = "구역")
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class CreateSectionController {

private final CreateSectionUsecase createSectionUsecase;

@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/stadiums/{stadiumId}/sections")
@Operation(summary = "특정 경기장의 구역 정보들을 생성한다.")
public void createAll(
@PathVariable @Positive @NotNull final Long stadiumId,
@RequestBody @Valid @NotEmpty List<CreateSectionRequest> requests) {
List<CreateSectionCommand> commands =
requests.stream().map(CreateSectionRequest::toCommand).toList();
createSectionUsecase.createAll(stadiumId, commands);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.depromeet.spot.application.section.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import org.depromeet.spot.application.common.dto.RgbCodeRequest;
import org.depromeet.spot.usecase.port.in.section.CreateSectionUsecase.CreateSectionCommand;
import org.hibernate.validator.constraints.Length;

import io.swagger.v3.oas.annotations.Parameter;

public record CreateSectionRequest(
@NotBlank
@Parameter(description = "구역 이름", example = "오렌지석")
@Length(max = 20, message = "구역 이름은 최대 20글자 까지만 입력할 수 있습니다.")
String name,
@Parameter(description = "구역 별칭", example = "응원석")
@Length(max = 20, message = "별칭은 최대 20글자 까지만 입력할 수 있습니다.")
String alias,
@NotNull RgbCodeRequest rgbCodeRequest) {

public CreateSectionCommand toCommand() {
return CreateSectionCommand.builder()
.name(name)
.alias(alias)
.labelRgbCode(rgbCodeRequest.toDomain())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
public enum SectionErrorCode implements ErrorCode {
SECTION_NOT_FOUND(HttpStatus.NOT_FOUND, "SE001", "요청 구역이 존재하지 않습니다."),
SECTION_NOT_BELONG_TO_STADIUM(HttpStatus.BAD_REQUEST, "SE002", "요청 경기장의 구역이 아닙니다."),
DUPLICATE_NAME(HttpStatus.BAD_REQUEST, "SE003", "요청 경기장에 이미 해당 이름의 구역이 존재합니다."),
DUPLICATE_ALIAS(HttpStatus.BAD_REQUEST, "SE004", "요청 경기장에 이미 해당 별칭의 구역이 존재합니다."),
;

private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,16 @@ public SectionNotBelongStadiumException() {
super(SectionErrorCode.SECTION_NOT_BELONG_TO_STADIUM);
}
}

public static class SectionNameDuplicateException extends SectionException {
public SectionNameDuplicateException() {
super(SectionErrorCode.DUPLICATE_NAME);
}
}

public static class SectionAliasDuplicateException extends SectionException {
public SectionAliasDuplicateException() {
super(SectionErrorCode.DUPLICATE_ALIAS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.depromeet.spot.jpa.section.repository;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import org.depromeet.spot.domain.section.Section;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class SectionJdbcRepository {

private final JdbcTemplate jdbcTemplate;

public void createSections(List<Section> sections) {
jdbcTemplate.batchUpdate(
"insert into sections"
+ "(stadium_id, name, alias, red, green, blue) values (?, ?, ?, ?, ?, ?)",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, sections.get(i).getStadiumId());
ps.setString(2, sections.get(i).getName());
ps.setString(3, sections.get(i).getAlias());
ps.setInt(4, sections.get(i).getLabelRgbCode().getRed());
ps.setInt(5, sections.get(i).getLabelRgbCode().getGreen());
ps.setInt(6, sections.get(i).getLabelRgbCode().getBlue());
}

@Override
public int getBatchSize() {
return sections.size();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
public class SectionRepositoryImpl implements SectionRepository {

private final SectionJpaRepository sectionJpaRepository;
private final SectionJdbcRepository sectionJdbcRepository;

@Override
public List<Section> findAllByStadium(final Long stadiumId) {
Expand All @@ -23,8 +24,13 @@ public List<Section> findAllByStadium(final Long stadiumId) {

@Override
public Section save(Section section) {
// TODO: test를 위해 추가 -> 구역 생성 티켓 작업할 때 구현 예정
return null;
SectionEntity entity = sectionJpaRepository.save(SectionEntity.from(section));
return entity.toDomain();
}

@Override
public void saveAll(List<Section> sections) {
sectionJdbcRepository.createSections(sections);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.depromeet.spot.usecase.port.in.section;

import java.util.List;

import org.depromeet.spot.domain.common.RgbCode;
import org.depromeet.spot.domain.section.Section;

import lombok.Builder;

public interface CreateSectionUsecase {

void createAll(Long stadiumId, List<CreateSectionCommand> commands);

@Builder
record CreateSectionCommand(String name, String alias, RgbCode labelRgbCode) {

public Section toDomain(final Long stadiumId) {
return Section.builder()
.stadiumId(stadiumId)
.name(name)
.alias(alias)
.labelRgbCode(labelRgbCode)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public interface SectionRepository {

Section save(Section section);

void saveAll(List<Section> sections);

boolean existsInStadium(Long stadiumId, Long sectionId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.depromeet.spot.usecase.service.section;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.depromeet.spot.common.exception.section.SectionException.SectionAliasDuplicateException;
import org.depromeet.spot.common.exception.section.SectionException.SectionNameDuplicateException;
import org.depromeet.spot.domain.section.Section;
import org.depromeet.spot.usecase.port.in.section.CreateSectionUsecase;
import org.depromeet.spot.usecase.port.in.stadium.StadiumReadUsecase;
import org.depromeet.spot.usecase.port.out.section.SectionRepository;
import org.springframework.stereotype.Service;

import lombok.Builder;
import lombok.RequiredArgsConstructor;

@Service
@Builder
@RequiredArgsConstructor
public class CreateSectionService implements CreateSectionUsecase {

private final SectionRepository sectionRepository;
private final StadiumReadUsecase stadiumReadUsecase;

@Override
public void createAll(final Long stadiumId, List<CreateSectionCommand> commands) {
validate(commands);
stadiumReadUsecase.checkIsExistsBy(stadiumId);

List<Section> sections = commands.stream().map(c -> c.toDomain(stadiumId)).toList();
sectionRepository.saveAll(sections);
}

private void validate(List<CreateSectionCommand> commands) {
List<String> names = new ArrayList<>();
List<String> aliases = new ArrayList<>();
commands.forEach(
command -> {
names.add(command.name());
// alias는 nullable하기 때문에 체크 필요 -> name은 request에서 NN validation 완료
if (command.alias() != null) {
aliases.add(command.alias());
}
});

checkIsDuplicateName(names);
checkIsDuplicateAlias(aliases);
}

public void checkIsDuplicateName(List<String> names) {
Set<String> namesSet = new HashSet<>(names);
if (namesSet.size() < names.size()) {
throw new SectionNameDuplicateException();
}
}

public void checkIsDuplicateAlias(List<String> aliases) {
Set<String> aliasSet = new HashSet<>(aliases);
if (aliasSet.size() < aliases.size()) {
throw new SectionAliasDuplicateException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public Section save(Section section) {
}
}

@Override
public void saveAll(List<Section> sections) {
sections.forEach(this::save);
}

@Override
public boolean existsInStadium(Long stadiumId, Long sectionId) {
return data.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.depromeet.spot.usecase.service.section;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.Arrays;
import java.util.List;

import org.depromeet.spot.common.exception.section.SectionException.SectionAliasDuplicateException;
import org.depromeet.spot.common.exception.section.SectionException.SectionNameDuplicateException;
import org.depromeet.spot.usecase.port.in.stadium.StadiumReadUsecase;
import org.depromeet.spot.usecase.service.fake.FakeSectionRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class CreateSectionServiceTest {

private CreateSectionService createSectionService;
private StadiumReadUsecase stadiumReadUsecase;

@BeforeEach
void init() {
FakeSectionRepository fakeSectionRepository = new FakeSectionRepository();
this.createSectionService =
CreateSectionService.builder()
.sectionRepository(fakeSectionRepository)
.stadiumReadUsecase(stadiumReadUsecase)
.build();
}

@Test
public void 요청에_이름과_별칭에_중복이_없다면_유효성_검증을_통과한다() {
// given
List<String> names = List.of("Section1", "Section2", "Section3");
List<String> aliases = Arrays.asList("Alias1", "Alias2", null);

// when
// then
assertDoesNotThrow(() -> createSectionService.checkIsDuplicateName(names));
assertDoesNotThrow(() -> createSectionService.checkIsDuplicateAlias(aliases));
}

@Test
void 요청에_중복된_이름이_있다면_에러를_반환한다() {
// given
List<String> names = List.of("Section1", "Section1", "Section2");

// when
// then
assertThrows(
SectionNameDuplicateException.class,
() -> createSectionService.checkIsDuplicateName(names));
}

@Test
void 요청에_중복된_별칭이_있다면_에러를_반환한다() {
// given
List<String> aliases = Arrays.asList(null, "Alias1", "Alias1");

// when
// then
assertThrows(
SectionAliasDuplicateException.class,
() -> createSectionService.checkIsDuplicateAlias(aliases));
}
}

0 comments on commit a674d24

Please sign in to comment.