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

[1 - 3단계 방탈출 사용자 예약] 에버(손채영) 미션 제출합니다. #35

Merged
merged 74 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
a994793
migrate first mission code
ehBeak Apr 20, 2024
d0856b4
chore(.gitignore): 커밋메시지 설정 파일 추가
helenason Apr 30, 2024
53fa07e
test(RoomescapeApplicationTest): 포트 설정 추가
helenason Apr 30, 2024
bd337eb
docs(README): 기능 요구사항 추가
helenason Apr 30, 2024
c033858
feat(reservationTime): 삭제 시 예약 시간이 존재하지 않는 경우 예외 발생
helenason Apr 30, 2024
b2b611c
feat(reservation): 삭제 시 예약이 존재하지 않는 경우 예외 발생
helenason Apr 30, 2024
b8f203e
feat(ReservationTimeRequest): 시간 생성 시 유효하지 않은 값 예외 처리
helenason Apr 30, 2024
b570b61
feat(ReservationRequest): 예약 생성 시 유효하지 않은 예약자명 예외 처리
helenason Apr 30, 2024
f5161b9
feat(ReservationRequest): 예약 생성 시 유효하지 않은 날짜 예외 처리
helenason May 1, 2024
4c54b7e
feat(ReservationService): 현재 이전 예약에 대한 예외 처리
helenason May 1, 2024
b87703d
feat(ReservationTimeService): 특정 시간에 대한 예약이 존재하는 경우, 그 시간을 삭제하려 할 때 예…
helenason May 1, 2024
03b8ec8
feat(ReservationTimeService): 예약 시간 중복 시 예외 처리
helenason May 1, 2024
2379446
feat(ReservationService): 예약 중복 시 예외 처리
helenason May 1, 2024
a46351f
docs(README): 2단계 기능 요구사항 추가
helenason May 1, 2024
ced8290
chore(schema): DDL 추가
helenason May 1, 2024
447436a
feat(StaticPageController): 테마 관리 페이지 조회
helenason May 1, 2024
1160177
feat(Theme): 테마 도메인 추가
helenason May 1, 2024
8376330
feat(theme): 테마 조회 API 구현
helenason May 1, 2024
c4e5225
feat(theme): 테마 추가 API 구현
helenason May 1, 2024
97dd0d3
feat(theme): 테마 삭제 API 구현
helenason May 1, 2024
50a1e4f
refactor(reservation): 테마 추가에 다른 예약 로직 수정
helenason May 1, 2024
1be9f4f
docs(README): 3단계 기능 요구사항 추가
helenason May 1, 2024
76afbf3
feat(StaticUserPageController): 사용자 예약 페이지 조회
helenason May 1, 2024
ac47daa
fix(js): 테마 목록 조회 후 렌더링
helenason May 1, 2024
1d5fe56
feat(Reservation): 테마와 날짜를 선택하면 예약 가능한 시간 조회 기능 구현
helenason May 1, 2024
b29334f
feat(StaticUserPageController): 인기 테마 페이지 조회
helenason May 2, 2024
1bcbf53
feat(theme): 인기 테마 조회 기능 구현
helenason May 2, 2024
e8785e7
feat(GlobalExceptionHandler): 예외 핸들러 구현
helenason May 2, 2024
f81521d
refactor(ReservationService): 예약 가능한 시간 조회 기능
helenason May 2, 2024
2ac28fd
style(all): 코드 컨벤션에 맞춰 수정
helenason May 2, 2024
838d102
test(reservation): 예약 가능 시간 조회 테스트
helenason May 2, 2024
f555d30
style(test): 인스턴스 필드 컨벤션 통일
helenason May 2, 2024
5113931
test(ReservationControllerTest): 특정 날짜와 테마에 따른 모든 시간의 예약 가능 여부 확인
helenason May 2, 2024
e548036
test(ReservationServiceTest): 현재로 예약하는 경우 경계값 테스트
helenason May 2, 2024
f2c69b7
refactor(request): 유효하지 않은 요청 시 예외 처리
helenason May 2, 2024
e21cfea
style(all): 코드 컨벤션에 맞춰 수정
helenason May 2, 2024
0cdf97e
refactor(controller): 엔드포인트 prefix 정의
helenason May 3, 2024
8944e92
refactor(dto): 응답 객체 생성 및 컨트롤러와 서비스 계층 DTO 분리
helenason May 3, 2024
1cb2140
refactor(repository): 쿼리에 따른 메서드 네이밍 통일
helenason May 3, 2024
8c4c09b
refactor(all): 메서드명 형식 통일
helenason May 3, 2024
1ac4a1b
refactor(all): repository와 dao 계층 분리
helenason May 3, 2024
a00a9c3
chore(dao): dao 계층 패키지 생성
helenason May 3, 2024
5cc17d8
refactor(all): 불필요한 dataSource 객체 제거
helenason May 3, 2024
19eac92
fix(ReservationTimeController): 다른 상태코드로 인한 렌더링 불가 문제
helenason May 3, 2024
ae30780
fix(ReservationResponse): 다른 필드명으로 인한 파싱 불가 문제
helenason May 3, 2024
767ccdd
refactor(ReservationService): 불필요한 로직 줄이기
helenason May 3, 2024
51e66ef
refactor(MemberReservationTimeResponse): 예약 상태 필드명 변경
helenason May 3, 2024
82f2fd6
refactor(dao): 데이터 존재 유무 확인을 위한 쿼리 변경
helenason May 3, 2024
277a0b8
refactor(ThemeService): 매직넘버 상수화
helenason May 3, 2024
4274210
refactor(dao): sql join 로직 분리
helenason May 5, 2024
0dca000
refactor(daoTest): 단위 테스트 보완 및 컨벤션 통일
helenason May 5, 2024
a3a52dd
test(repositoryTest): 레포지토리 클래스
helenason May 5, 2024
7785cd1
chore(daoTest): 테스트 내 패키지 생성
helenason May 5, 2024
e30d987
refactor(serviceTest): 테스트 컨벤션 통일
helenason May 5, 2024
3370afa
refactor(FakeReservationDao): 불필요한 주석 및 코드 제거
helenason May 5, 2024
45eeca6
fix(js): 테마 아이디 렌더링 문제 해결
helenason May 5, 2024
fd50d94
refactor(controllerTest): 테스트 컨벤션 통일
helenason May 6, 2024
0f1db66
refactor(GlobalExceptionHandler): 동일한 로직으로 처리되는 에러 핸들러 통합
helenason May 6, 2024
ef4bcc7
refactor(ReservationDao): 데이터 저장 시 DTO를 전달받도록
helenason May 6, 2024
6211381
test(fakedao): DAO의 fake 객체에 대한 테스트
helenason May 6, 2024
2bf9a5d
fix(controller): 요청 데이터에 대해 null 값 허용
helenason May 6, 2024
b619d2f
fix(GlobalExceptionHandler): 모든 런타임 예외 처리
helenason May 6, 2024
b6d69bf
refactor(model): toString 메서드 선언
helenason May 6, 2024
d1dc05e
refactor(MemberReservationTimeResponse): equals&hashCode 제거
helenason May 6, 2024
4967160
style(all): 불필요한 import문 제거
helenason May 6, 2024
a900183
refactor(sql): 더미데이터 생성 로직 파일 분리
helenason May 6, 2024
d21c727
refactor(response): 브라우저에 반환하는 시간 데이터 포맷 변경
helenason May 6, 2024
ce753ce
refactor(request): 요청된 데이터 검증 구체화
helenason May 6, 2024
fe8c42d
feat(model): 도메인 검증 로직
helenason May 6, 2024
ea5184a
refactor(controller): 요청 데이터 검증 로직 컨트롤러로 이동
helenason May 6, 2024
e22af93
feat(service): 서비스 계층 검증 로직 추가
helenason May 6, 2024
a85a0a0
fix(ReservationService): 예약의 중복 검증 시 테마 검증 추가
helenason May 7, 2024
6853ef7
refactor(sql): UNIQUE 옵션 추가
helenason May 7, 2024
f0bde31
refactor(test): 불필요한 DirtiesContext 어노테이션 제거
helenason May 7, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.gitmessage.txt

HELP.md
.gradle
build/
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## 기능 요구사항
- [x] 예외 처리
- [x] 시간 생성 시 시작 시간에 유효하지 않은 값이 입력되었을 때
- null, "", HH:mm이 아닌 경우
- [x] 예약 생성 시 예약자명, 날짜, 시간에 유효하지 않은 값이 입력 되었을 때
- 예약자명: null, ""
- 날짜: null, "", yyyy-MM-dd이 아닌 경우
- 시간: null, "", HH:mm이 아닌 경우
- [x] 특정 시간에 대한 예약이 존재하는데, 그 시간을 삭제하려 할 때
- [x] 존재하지 않는 id를 가진 데이터에 접근하려 할 때
- [x] 지나간 날짜와 시간에 대한 예약 생성 불가능
- [x] 중복 예약 불가능
- [x] 예약 시간 중복 불가능

- [x] '테마' 도메인 추가
- [x] 모든 테마는 시작 시간과 소요 시간이 동일
- [x] 테마 관리 페이지 조회
- [x] 테마 추가 API
- [x] 테마 삭제 API
- [x] 테마 조회 API

- [x] (관리자가 아닌) 사용자가 예약 가능한 시간을 조회하고, 예약할 수 있도록 기능을 추가/변경
- [x] 테마와 날짜를 선택하면 예약 가능한 시간 조회
- [x] 예약 추가
- [X] /reservation 요청 시 사용자 예약 페이지 조회
- [x] 인기 테마 조회 기능을 추가
- [x] 최근 일주일을 기준으로 하여 해당 기간 내에 방문하는 예약이 많은 테마 10개를 확인
- [x] / 요청 시 인기 테마 페이지 조회
1 change: 0 additions & 1 deletion src/main/java/roomescape/RoomescapeApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ public class RoomescapeApplication {
public static void main(String[] args) {
SpringApplication.run(RoomescapeApplication.class, args);
}

}
68 changes: 68 additions & 0 deletions src/main/java/roomescape/controller/ReservationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package roomescape.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.controller.request.ReservationRequest;
import roomescape.controller.response.MemberReservationTimeResponse;
import roomescape.controller.response.ReservationResponse;
import roomescape.exception.BadRequestException;
import roomescape.model.Reservation;
import roomescape.service.ReservationService;
import roomescape.service.dto.ReservationDto;

import java.net.URI;
import java.time.LocalDate;
import java.util.List;

@RestController
@RequestMapping("/reservations")
public class ReservationController {

private final ReservationService reservationService;

public ReservationController(ReservationService reservationService) {
this.reservationService = reservationService;
}

@GetMapping
public ResponseEntity<List<ReservationResponse>> showReservations() {
List<Reservation> reservations = reservationService.findAllReservations();
List<ReservationResponse> response = reservations.stream()
.map(ReservationResponse::from)
.toList();
return ResponseEntity.ok(response);
}

@PostMapping
public ResponseEntity<ReservationResponse> addReservation(@RequestBody ReservationRequest request) {
ReservationDto reservationDto = ReservationDto.from(request);
Reservation reservation = reservationService.saveReservation(reservationDto);
ReservationResponse response = ReservationResponse.from(reservation);
return ResponseEntity
.created(URI.create("/reservations/" + response.getId()))
.body(response);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteReservation(@PathVariable("id") Long id) {
validateNull(id);
reservationService.deleteReservation(id);
return ResponseEntity.noContent().build();
}

@GetMapping("/times")
public ResponseEntity<List<MemberReservationTimeResponse>> showReservationTimesInformation(
@RequestParam(name = "date") LocalDate date,
@RequestParam(name = "themeId") Long themeId) {
validateNull(themeId);
List<MemberReservationTimeResponse> response = reservationService.findReservationTimesInformation(date, themeId);
// TODO: 여기서 response 객체로 반환하도록 수정
return ResponseEntity.ok(response);
}

private void validateNull(Long value) {
if (value == null) {
throw new BadRequestException("[ERROR] 요청된 데이터에 null 혹은 비어있는 값이 존재합니다.");
}
}
}
56 changes: 56 additions & 0 deletions src/main/java/roomescape/controller/ReservationTimeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package roomescape.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.controller.request.ReservationTimeRequest;
import roomescape.controller.response.ReservationTimeResponse;
import roomescape.exception.BadRequestException;
import roomescape.model.ReservationTime;
import roomescape.service.ReservationTimeService;
import roomescape.service.dto.ReservationTimeDto;

import java.net.URI;
import java.util.List;

@RestController
@RequestMapping("/times")
public class ReservationTimeController {

private final ReservationTimeService reservationTimeService;

public ReservationTimeController(ReservationTimeService reservationTimeService) {
this.reservationTimeService = reservationTimeService;
}

@GetMapping
public ResponseEntity<List<ReservationTimeResponse>> showReservationTimes() {
List<ReservationTime> times = reservationTimeService.findAllReservationTimes();
List<ReservationTimeResponse> response = times.stream()
.map(ReservationTimeResponse::from)
.toList();
return ResponseEntity.ok(response);
}

@PostMapping
public ResponseEntity<ReservationTimeResponse> addReservationTime(@RequestBody ReservationTimeRequest request) {
ReservationTimeDto timeDto = ReservationTimeDto.from(request);
ReservationTime time = reservationTimeService.saveReservationTime(timeDto);
ReservationTimeResponse response = ReservationTimeResponse.from(time);
return ResponseEntity
.created(URI.create("/times/" + response.getId()))
.body(response);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteReservationTime(@PathVariable("id") Long id) {
validateNull(id);
reservationTimeService.deleteReservationTime(id);
return ResponseEntity.noContent().build();
}

private void validateNull(Long value) {
if (value == null) {
throw new BadRequestException("[ERROR] 요청된 데이터에 null 혹은 비어있는 값이 존재합니다.");
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/roomescape/controller/StaticAdminPageController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package roomescape.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/admin")
public class StaticAdminPageController {
@GetMapping
public String getAdminPage() {
return "admin/index";
}

@GetMapping("/reservation")
public String getReservationPage() {
return "admin/reservation-new";
}

@GetMapping("/time")
public String getReservationTimePage() {
return "admin/time";
}

@GetMapping("/theme")
public String getThemePage() {
return "admin/theme";
}
}
18 changes: 18 additions & 0 deletions src/main/java/roomescape/controller/StaticUserPageController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package roomescape.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class StaticUserPageController {

@GetMapping
public String getHome() {
return "index";
}

@GetMapping("/reservation")
public String getReservation() {
return "reservation";
}
}
65 changes: 65 additions & 0 deletions src/main/java/roomescape/controller/ThemeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package roomescape.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.controller.request.ThemeRequest;
import roomescape.controller.response.ThemeResponse;
import roomescape.exception.BadRequestException;
import roomescape.model.Theme;
import roomescape.service.ThemeService;
import roomescape.service.dto.ThemeDto;

import java.net.URI;
import java.util.List;

@RestController
@RequestMapping("/themes")
public class ThemeController {

private final ThemeService themeService;

public ThemeController(ThemeService themeService) {
this.themeService = themeService;
}

@GetMapping
public ResponseEntity<List<ThemeResponse>> showThemes() {
List<Theme> themes = themeService.findAllThemes();
List<ThemeResponse> response = themes.stream()
.map(ThemeResponse::from)
.toList();
return ResponseEntity.ok(response);
}

@PostMapping
public ResponseEntity<ThemeResponse> addTheme(@RequestBody ThemeRequest request) {
ThemeDto themeDto = ThemeDto.from(request);
Theme theme = themeService.saveTheme(themeDto);
ThemeResponse response = ThemeResponse.from(theme);
return ResponseEntity
.created(URI.create("/themes/" + response.getId()))
.body(response);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTheme(@PathVariable("id") Long id) {
validateNull(id);
themeService.deleteTheme(id);
return ResponseEntity.noContent().build();
}

@GetMapping("/rank")
public ResponseEntity<List<ThemeResponse>> showPopularThemes() {
List<Theme> themes = themeService.findPopularThemes();
List<ThemeResponse> response = themes.stream()
.map(ThemeResponse::from)
.toList();
return ResponseEntity.ok(response);
}

private void validateNull(Long value) {
if (value == null) {
throw new BadRequestException("[ERROR] 요청된 데이터에 null 혹은 비어있는 값이 존재합니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package roomescape.controller.request;

import roomescape.exception.BadRequestException;

import java.time.LocalDate;

public class ReservationRequest {

private final String name;
private final LocalDate date;
private final long timeId;
private final long themeId;

public ReservationRequest(String name, LocalDate date, Long timeId, Long themeId) {
validate(name, date, timeId, themeId);
this.name = name;
this.date = date;
this.timeId = timeId;
this.themeId = themeId;
}

private void validate(String name, LocalDate date, Long timeId, Long themeId) {
validateName(name);
validateNull(date);
validateNull(timeId);
validateNull(themeId);
}

private void validateNull(Object value) {
if (value == null) {
throw new BadRequestException("[ERROR] 요청된 데이터에 null 혹은 비어있는 값이 존재합니다.");
}
}

private void validateName(String name) {
if (name == null || name.isBlank()) {
throw new BadRequestException("[ERROR] 요청된 데이터에 null 혹은 비어있는 값이 존재합니다.");
}
}

public LocalDate getDate() {
return date;
}

public String getName() {
return name;
}

public long getTimeId() {
return timeId;
}

public long getThemeId() {
return themeId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package roomescape.controller.request;

import roomescape.exception.BadRequestException;

import java.time.LocalTime;

public class ReservationTimeRequest {

private LocalTime startAt;

public ReservationTimeRequest(LocalTime startAt) {
validateNull(startAt);
this.startAt = startAt;
}

private ReservationTimeRequest() {
}
her0807 marked this conversation as resolved.
Show resolved Hide resolved

private void validateNull(LocalTime startAt) {
if (startAt == null) {
throw new BadRequestException("[ERROR] 요청된 데이터에 null 혹은 비어있는 값이 존재합니다.");
}
}

public LocalTime getStartAt() {
return startAt;
}
}
Loading