Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spring 경로 조회 - 1단계] 유콩(김유빈) 미션 제출합니다. #179

Merged
merged 36 commits into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0f417f0
chore(src/): 이전 미션 코드 추가
skullkim May 17, 2022
ca5414b
docs(README.md): 요구사항 추가
skullkim May 17, 2022
65dc358
feat(PathCalculator.java): 최단 경로를 구하는 기능 완료
skullkim May 17, 2022
c966798
feat(PathCalculator.java): 최단경로의 거리를 계산하는 기능 완료
skullkim May 17, 2022
5fd38b7
refactor(PathCalculator.java): 중복 코드 제거
skullkim May 17, 2022
3f46c10
feat(PathResponse.java): 경로 조회 응답 객체에 최단거리 필드 추가
skullkim May 17, 2022
aaa5d01
feat(Fare.java): 거리에 맞는 금액을 계산하는 기능 완료
skullkim May 17, 2022
1b7d967
feat(PathResponse.java) : 경로 조회 응답 DTO에 요금 정보 추가
skullkim May 17, 2022
9f61932
feat(PathController.java): 경로 관련 요청을 처리하는 컨트롤러 완료
skullkim May 17, 2022
ead7568
fix(PathController.java): 경로 수정
skullkim May 17, 2022
f073cec
test(PathAcceptanceTest.java): 경로 관련 인수 테스트 완료
skullkim May 17, 2022
edc24ef
docs: 구현한 요구사항 체크
kyukong May 17, 2022
21bcab4
refactor: @ModelAttribute 이용하여 객체로 받아오도록 수정
kyukong May 17, 2022
01a88df
refactor: 프로젝트에 관계없는 파일 삭제
kyukong May 20, 2022
060c046
refactor: 프로젝트에 관계없는 파일 삭제
kyukong May 20, 2022
9afa1bd
chore(.gitignore): .DS_Store 확장자 추가
kyukong May 20, 2022
68c2e35
refactor: ui 패키지 이름 controller 로 수정
kyukong May 20, 2022
4e978b6
refactor: @RequestMapping 이용하여 공통 URI 설정
kyukong May 20, 2022
449ebac
chore(build.gradle): validation 의존성 추가
kyukong May 20, 2022
32b4831
refactor: Line 수정 시 생성하는 DTO 별도 생성
kyukong May 20, 2022
e5dfcf5
refactor: DTO 객체 검증 추가
kyukong May 20, 2022
5bdf8f7
refactor: Line 요청 DTO 관련 검증 요류 메세지 설정
kyukong May 20, 2022
be051ad
refactor: Path 요청 DTO 관련 검증 요류 메세지 설정
kyukong May 20, 2022
cc60f93
refactor: Section 요청 DTO 관련 검증 요류 메세지 설정
kyukong May 20, 2022
99f1469
refactor: Station 요청 DTO 관련 검증 요류 메세지 설정
kyukong May 20, 2022
8c12e42
refactor: DAO 테스트 시 @Transactional 삭제
kyukong May 20, 2022
b2068e7
refactor: DAO 인터페이스 삭제
kyukong May 20, 2022
66ad884
refactor: 클래스단에 @Transactional 설정
kyukong May 20, 2022
961abc9
refactor: 지하철 경로 거리 및 요금 계산에서 값의 타입을 double 형으로 수정
kyukong May 20, 2022
1b1f1e8
refactor(Fare.java): 상수명 변경 및 로직 리팩토링
kyukong May 20, 2022
7ec3a33
refactor: repository 계층 삭제
kyukong May 21, 2022
0b5a52e
refactor: 사용하지 않는 파일 및 패키지 삭제
kyukong May 21, 2022
97f3d8c
refactor: 요금 및 경로 관련 추가
kyukong May 21, 2022
2841af3
refactor: 사용하지 않는 파일 삭제
kyukong May 21, 2022
81dc42a
refactor: DTO 를 사용하는 계층으로 이동
kyukong May 21, 2022
8faa3c0
refactor: 예외처리 추가 및 리픽토링
kyukong May 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ out/

### VS Code ###
.vscode/

### mac ###
.DS_Store
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@

<br>

# 지하철 노선도 미션
# 🚇 지하철 노선도 미션
스프링 과정 실습을 위한 지하철 노선도 애플리케이션

## 요구 사항
- [x] 그래프의 최단거리를 구하는 기능
- [x] 최단 경로 목록 조회
- [x] 최단 거리 조회
- [x] 거리에 따른 요금 계산 기능
- [x] 기본운임(10㎞ 이내): 기본운임 1,250원
- [x] 이용 거리 초과 시 추가운임 부과
- [x] 10km~50km: 5km 까지 마다 100원 추가
- [x] 50km 초과: 8km 까지 마다 100원 추가

<br>

## 🚀 Getting Started
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
// spring
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// log
implementation 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1'
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/wooteco/subway/controller/ControllerAdvice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package wooteco.subway.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import wooteco.subway.controller.dto.ErrorResponse;

@RestControllerAdvice
public class ControllerAdvice {

@ExceptionHandler({Exception.class, RuntimeException.class})
public ResponseEntity<ErrorResponse> unexpectedError() {
return ResponseEntity.badRequest().body(ErrorResponse.from("실행할 수 없는 명령입니다."));
}

@ExceptionHandler({IllegalStateException.class})
public ResponseEntity<ErrorResponse> duplicateStation(final Exception exception) {
return ResponseEntity.badRequest().body(ErrorResponse.from(exception));
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(final Exception exception) {
ErrorResponse errorResponse = ErrorResponse.from(exception);
return ResponseEntity.badRequest().body(errorResponse);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(final MethodArgumentNotValidException exception) {
ErrorResponse errorResponse = ErrorResponse.from(exception);
return ResponseEntity.badRequest().body(errorResponse);
}
}
61 changes: 61 additions & 0 deletions src/main/java/wooteco/subway/controller/LineController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package wooteco.subway.controller;

import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import wooteco.subway.service.dto.request.LineSaveRequest;
import wooteco.subway.service.dto.request.LineUpdateRequest;
import wooteco.subway.service.dto.response.LineResponse;
import wooteco.subway.service.LineService;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@RestController
@RequestMapping("/lines")
public class LineController {

private final LineService lineService;

public LineController(LineService lineService) {
this.lineService = lineService;
}

@PostMapping
public ResponseEntity<LineResponse> createLine(@RequestBody @Valid LineSaveRequest lineSaveRequest) {
LineResponse lineResponse = lineService.save(lineSaveRequest);
return ResponseEntity.created(URI.create("/lines/" + lineResponse.getId())).body(lineResponse);
}

@GetMapping
public ResponseEntity<List<LineResponse>> showLines() {
List<LineResponse> lineResponses = lineService.findAll();
return ResponseEntity.ok().body(lineResponses);
}

@GetMapping("/{id}")
public ResponseEntity<LineResponse> showLine(@PathVariable @NotNull Long id) {
LineResponse lineResponse = lineService.find(id);
return ResponseEntity.ok().body(lineResponse);
}

@PutMapping("/{id}")
public ResponseEntity<Void> updateLine(@PathVariable @NotNull Long id, @RequestBody @Valid LineUpdateRequest lineUpdateRequest) {
lineService.update(id, lineUpdateRequest);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteLine(@PathVariable @NotNull Long id) {
lineService.delete(id);
return ResponseEntity.noContent().build();
}
}
29 changes: 29 additions & 0 deletions src/main/java/wooteco/subway/controller/PathController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package wooteco.subway.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import wooteco.subway.service.dto.request.PathRequest;
import wooteco.subway.service.dto.response.PathResponse;
import wooteco.subway.service.PathService;

import javax.validation.Valid;

@RestController
@RequestMapping("/paths")
public class PathController {

private final PathService pathService;

public PathController(final PathService pathService) {
this.pathService = pathService;
}

@GetMapping
public ResponseEntity<PathResponse> showPath(@ModelAttribute @Valid PathRequest pathRequest) {
final PathResponse pathResponse = pathService.findShortestPath(pathRequest.getSource(), pathRequest.getTarget());
return ResponseEntity.ok().body(pathResponse);
}
}
38 changes: 38 additions & 0 deletions src/main/java/wooteco/subway/controller/SectionController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package wooteco.subway.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestParam;
import wooteco.subway.service.dto.request.SectionRequest;
import wooteco.subway.service.SectionService;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@Controller
@RequestMapping("/lines/{lineId}/sections")
public class SectionController {

private final SectionService sectionService;

public SectionController(SectionService sectionService) {
this.sectionService = sectionService;
}

@PostMapping
public ResponseEntity<Void> createSection(@PathVariable @NotNull Long lineId, @RequestBody @Valid SectionRequest sectionRequest) {
sectionService.save(lineId, sectionRequest);
return ResponseEntity.ok().build();
}

@DeleteMapping
public ResponseEntity<Void> deleteSection(@PathVariable @NotNull Long lineId, @RequestParam @NotNull Long stationId) {
sectionService.delete(lineId, stationId);
return ResponseEntity.ok().build();
}
}
48 changes: 48 additions & 0 deletions src/main/java/wooteco/subway/controller/StationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package wooteco.subway.controller;

import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import wooteco.subway.service.dto.request.StationRequest;
import wooteco.subway.service.dto.response.StationResponse;
import wooteco.subway.service.StationService;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@RestController
@RequestMapping("/stations")
public class StationController {

private final StationService stationService;

public StationController(StationService stationService) {
this.stationService = stationService;
}

@PostMapping
public ResponseEntity<StationResponse> createStation(@RequestBody @Valid StationRequest stationRequest) {
long stationId = stationService.save(stationRequest);
StationResponse stationResponse = new StationResponse(stationId, stationRequest.getName());
return ResponseEntity.created(URI.create("/stations/" + stationId)).body(stationResponse);
}

@GetMapping
public ResponseEntity<List<StationResponse>> showStations() {
List<StationResponse> stationResponses = stationService.findAll();
return ResponseEntity.ok().body(stationResponses);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteStation(@PathVariable @NotNull Long id) {
stationService.delete(id);
return ResponseEntity.noContent().build();
}
}
37 changes: 37 additions & 0 deletions src/main/java/wooteco/subway/controller/dto/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package wooteco.subway.controller.dto;

import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.web.bind.MethodArgumentNotValidException;

import java.util.stream.Collectors;

public class ErrorResponse {

private static final String ERROR_MESSAGE_DELIMITER = ",";

private String message;

private ErrorResponse(final String message) {
this.message = message;
}

public static ErrorResponse from(final String message) {
return new ErrorResponse(message);
}

public static ErrorResponse from(final Exception exception) {
return new ErrorResponse(exception.getMessage());
}

public static ErrorResponse from(final MethodArgumentNotValidException exception) {
return new ErrorResponse(exception.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(ERROR_MESSAGE_DELIMITER)));
}

public String getMessage() {
return message;
}
}
106 changes: 106 additions & 0 deletions src/main/java/wooteco/subway/dao/LineDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package wooteco.subway.dao;

import java.sql.PreparedStatement;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import wooteco.subway.domain.Line;
import wooteco.subway.domain.Station;

@Repository
public class LineDao {

private static final RowMapper<Line> LINE_ROW_MAPPER = (resultSet, rowNum) -> {
return new Line(
resultSet.getLong("id"),
resultSet.getString("name"),
resultSet.getString("color")
);
};

private static final RowMapper<Station> STATION_ROW_MAPPER = (resultSet, rowNum) -> {
return new Station(
resultSet.getLong("id"),
resultSet.getString("name")
);
};
kyukong marked this conversation as resolved.
Show resolved Hide resolved

private final JdbcTemplate jdbcTemplate;

public LineDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public long save(final Line line) {
final String sql = "insert into LINE (name, color) values (?, ?)";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement preparedStatement = connection.prepareStatement(sql, new String[]{"id"});
preparedStatement.setString(1, line.getName());
preparedStatement.setString(2, line.getColor());
return preparedStatement;
}, keyHolder);

return Objects.requireNonNull(keyHolder.getKey()).longValue();
}

public boolean existLineById(final Long id) {
final String sql = "select exists (select * from LINE where id = ?)";
return jdbcTemplate.queryForObject(sql, Boolean.class, id);
}

public boolean existLineByName(final String name) {
final String sql = "select exists (select * from LINE where name = ?)";
return jdbcTemplate.queryForObject(sql, Boolean.class, name);
}

public boolean existLineByColor(final String color) {
final String sql = "select exists (select * from LINE where color = ?)";
return jdbcTemplate.queryForObject(sql, Boolean.class, color);
}

public List<Line> findAll() {
final String sql = "select id, name, color from LINE";
return jdbcTemplate.query(sql, LINE_ROW_MAPPER);
}

public List<Station> findStations(final Long id) {
final String sql = "SELECT STATION.id, name FROM STATION " +
"JOIN " +
"( " +
"(SELECT up_station_id as id FROM SECTION WHERE line_id = ?) " +
"UNION " +
"(SELECT down_station_id as id FROM SECTION WHERE line_id = ?) " +
") " +
"AS STATION_IN_LINE " +
"ON STATION.id = STATION_IN_LINE.id";
return jdbcTemplate.query(sql, STATION_ROW_MAPPER, id, id);
}

public Optional<Line> find(final Long id) {
final String sql = "select id, name, color from LINE where id = ?";
try {
return Optional.ofNullable(jdbcTemplate.queryForObject(sql, LINE_ROW_MAPPER, id));
} catch (EmptyResultDataAccessException exception) {
return Optional.empty();
}
}

public void update(final long id, final Line line) {
final String sql = "update LINE set name = ?, color = ? where id = ?";
jdbcTemplate.update(sql, line.getName(), line.getColor(), id);
}

public void delete(final Long id) {
final String sql = "delete from LINE where id = ?";
jdbcTemplate.update(sql, id);
}
}
Loading