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

[BE] 기간 지정 팀플레이스 일정 조회 기능 구현 #95

Merged
merged 11 commits into from
Jul 20, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.transaction.annotation.Transactional;
import team.teamby.teambyteam.schedule.application.dto.ScheduleRegisterRequest;
import team.teamby.teambyteam.schedule.application.dto.ScheduleResponse;
import team.teamby.teambyteam.schedule.application.dto.SchedulesResponse;
import team.teamby.teambyteam.schedule.domain.Schedule;
import team.teamby.teambyteam.schedule.domain.ScheduleRepository;
import team.teamby.teambyteam.schedule.domain.Span;
Expand All @@ -13,11 +14,20 @@
import team.teamby.teambyteam.teamplace.domain.TeamPlaceRepository;
import team.teamby.teambyteam.teamplace.exception.TeamPlaceException;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class ScheduleService {

private static final LocalTime START_TIME_OF_DAY = LocalTime.of(0, 0, 0);
private static final LocalTime END_TIME_OF_DAY = LocalTime.of(23, 59, 59);

private final ScheduleRepository scheduleRepository;
private final TeamPlaceRepository teamPlaceRepository;

Expand Down Expand Up @@ -61,4 +71,19 @@ private void validateScheduleOwnerTeam(final Long teamPlaceId, final Schedule sc
private boolean isNotScheduleOfTeam(final Long teamPlaceId, final Schedule schedule) {
return !schedule.isScheduleOfTeam(teamPlaceId);
}

public SchedulesResponse findScheduleIn(final Long teamPlaceId, final int targetYear, final int targetMonth) {
// TODO: 상의해보기 - 팀플레이스 소속 멤버 검증시 팀플레이스 아이디가 검증이 될 건데 해당 붑ㄴ에 대한 재 검증이 필요한가?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interceptor에서 검증이 된다고 하면 Service로 들어오기 전에 teamPlaceId가 오염될 수도 있다고 생각합니다!
예를 들면 Controller 단에서 조작된 teamPlaceId가 들어올 수도 있을 것 같습니다!
물론 이런 경우는 거의 없겠지만 가능성이 존재하니 중복 검증해도 괜찮다고 생각합니다!

checkTeamPlaceExist(teamPlaceId);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 의견 남겨주시면 감사하겠습니다!!!


final LocalDate startDate = LocalDate.of(targetYear, targetMonth, 1);
final LocalDate endDAte = startDate.with(TemporalAdjusters.lastDayOfMonth());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 변수명 endDAte -> endDate로 수정되어야 할 것 같습니다!

final LocalDateTime startDateTime = LocalDateTime.of(startDate, START_TIME_OF_DAY);
final LocalDateTime endDateTime = LocalDateTime.of(endDAte, END_TIME_OF_DAY);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 잘 이해가 안돼서 코멘트로 설명해주시면 좋을 것 같아요!


final List<Schedule> schedules = scheduleRepository
.findAllByTeamPlaceInPeriod(teamPlaceId, startDateTime, endDateTime);

return SchedulesResponse.of(schedules);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package team.teamby.teambyteam.schedule.application.dto;

import team.teamby.teambyteam.schedule.domain.Schedule;

import java.util.List;

public record SchedulesResponse(
List<ScheduleResponse> schedules
) {
public static SchedulesResponse of(final List<Schedule> schedules) {
return new SchedulesResponse(schedules.stream()
.map(ScheduleResponse::of)
.toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
package team.teamby.teambyteam.schedule.domain;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.time.LocalDateTime;
import java.util.List;

public interface ScheduleRepository extends JpaRepository<Schedule, Long> {

@Query("SELECT s FROM Schedule s " +
"WHERE s.teamPlaceId = :teamPlaceId " +
"AND s.span.startDateTime < :lastDateTime " +
"AND s.span.endDateTime > :firstDateTime"
)
List<Schedule> findAllByTeamPlaceInPeriod(
Long teamPlaceId,
LocalDateTime firstDateTime,
LocalDateTime lastDateTime
);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -11,6 +12,7 @@
@Embeddable
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
public class Title {

@Column(name = "title", nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import team.teamby.teambyteam.member.configuration.AuthPrincipal;
import team.teamby.teambyteam.member.configuration.dto.MemberEmailDto;
import team.teamby.teambyteam.schedule.application.ScheduleService;
import team.teamby.teambyteam.schedule.application.dto.ScheduleRegisterRequest;
import team.teamby.teambyteam.schedule.application.dto.ScheduleResponse;
import team.teamby.teambyteam.schedule.application.dto.SchedulesResponse;

import java.net.URI;

Expand All @@ -21,15 +20,25 @@ public class TeamPlaceScheduleController {

@GetMapping("/{teamPlaceId}/calendar/schedules/{scheduleId}")
public ResponseEntity<ScheduleResponse> findSpecificSchedule(
@AuthPrincipal final MemberEmailDto memberEmailDto,
@PathVariable("teamPlaceId") final Long teamPlaceId,
@PathVariable("scheduleId") final Long scheduleId
@PathVariable final Long teamPlaceId,
@PathVariable final Long scheduleId
) {
final ScheduleResponse responseBody = scheduleService.findSchedule(scheduleId, teamPlaceId);

return ResponseEntity.ok(responseBody);
}

@GetMapping("/{teamPlaceId}/calendar/schedules")
public ResponseEntity<SchedulesResponse> findSchedulesInPeriod(
@PathVariable final Long teamPlaceId,
@RequestParam final Integer year,
@RequestParam final Integer month
) {
final SchedulesResponse responseBody = scheduleService.findScheduleIn(teamPlaceId, year, month);

return ResponseEntity.ok(responseBody);
}

@PostMapping("/{teamPlaceId}/calendar/schedules")
public ResponseEntity<Void> register(
@RequestBody @Valid final ScheduleRegisterRequest scheduleRegisterRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import org.springframework.http.MediaType;
import team.teamby.teambyteam.common.AcceptanceTest;
import team.teamby.teambyteam.schedule.application.dto.ScheduleRegisterRequest;
import team.teamby.teambyteam.schedule.application.dto.ScheduleResponse;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -39,7 +41,7 @@ public class ScheduleAcceptanceTest extends AcceptanceTest {

@Nested
@DisplayName("팀플레이스 내 특정 일정 조회")
class FindTest {
class FindTeamPlaceSpecificSchedule {

@Test
@DisplayName("팀에서 아이디를 가지고 특정 일정을 조회한다.")
Expand Down Expand Up @@ -88,6 +90,69 @@ void failGetScheduleWhichIsNot() {
}
}

@Nested
@DisplayName("팀플래이스 내 기간 일정 조회 시")
class FindTeamPlaceScheduleInPeriod {

@Test
@DisplayName("기간으로 조회 성공한다.")
void success() {
// given
final Long teamPlaceId = 3L;
final int year = 2023;
final int month = 7;

// when
final ExtractableResponse<Response> response = requestTeamPlaceSchedulesInPeriod(teamPlaceId, year, month);
final List<ScheduleResponse> schedules = response.jsonPath().getList("schedules", ScheduleResponse.class);

//then
assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
softly.assertThat(schedules.get(0).title()).isEqualTo("3번 팀플 B");
softly.assertThat(schedules.get(1).title()).isEqualTo("3번 팀플 C");
softly.assertThat(schedules.get(2).title()).isEqualTo("3번 팀플 D");
});
}

@Test
@DisplayName("요청에 패스쿼리가 누락되면 조회에 실패한다.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

패스 쿼리가 뭔가요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

날짜정보라도 표현 수정했습니다!!

void failWithMissingQuery() {
// given
final Long teamPlaceId = 3L;
final int month = 7;

// when
final ExtractableResponse<Response> response = requestTeamPlaceSchedulesInPeriod(teamPlaceId, null, month);

//then
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}

@Test
@DisplayName("기간 요쳥에 잘못된 형식으로 요쳥되면 조회에 실패한다.")
void failWithWrongQuery() {
// given
final Long teamPlaceId = 3L;
final String year = "y2023";
final int month = 7;

// when
final ExtractableResponse<Response> response = RestAssured.given().log().all()
.header(new Header("Authorization", JWT_PREFIX + JWT_TOKEN))
.pathParam("teamPlaceId", teamPlaceId)
.queryParam("year", year)
.queryParam("month", month)
.when().log().all()
.get("/api/team-place/{teamPlaceId}/calendar/schedules")
.then().log().all()
.extract();

//then
assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
}

@Nested
@DisplayName("일정 등록 시")
class RegisterSchedule {
Expand Down Expand Up @@ -195,4 +260,16 @@ private ExtractableResponse<Response> requestSpecificSchedule(final Long schedul
.then().log().all()
.extract();
}

private ExtractableResponse<Response> requestTeamPlaceSchedulesInPeriod(final Long teamPlaceId, final Integer year, final Integer month) {
return RestAssured.given().log().all()
.header(new Header("Authorization", JWT_PREFIX + JWT_TOKEN))
.pathParam("teamPlaceId", teamPlaceId)
.queryParam("year", year)
.queryParam("month", month)
.when().log().all()
.get("/api/team-place/{teamPlaceId}/calendar/schedules")
.then().log().all()
.extract();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
import team.teamby.teambyteam.fixtures.ScheduleFixtures;
import team.teamby.teambyteam.schedule.application.dto.ScheduleRegisterRequest;
import team.teamby.teambyteam.schedule.application.dto.ScheduleResponse;
import team.teamby.teambyteam.schedule.application.dto.SchedulesResponse;
import team.teamby.teambyteam.schedule.exception.ScheduleException;
import team.teamby.teambyteam.teamplace.exception.TeamPlaceException;

import java.time.LocalDateTime;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
Expand All @@ -29,8 +32,8 @@ class ScheduleServiceTest {


@Nested
@DisplayName("일정 정보 조회시")
class FindSchedule {
@DisplayName("팀플레이스 일정 정보 조회시")
class FindScheduleInTeamPlace {

@Test
@DisplayName("특정 일정의 정보를 조회한다.")
Expand Down Expand Up @@ -76,6 +79,44 @@ void failFindOtherTeamPlaceSchedule() {
.isInstanceOf(ScheduleException.TeamAccessForbidden.class)
.hasMessage("해당 팀플레이스에 일정을 조회할 권한이 없습니다.");
}

@Test
@DisplayName("팀플레이스에서 특정 기간 내 일정들을 조회한다.")
void findAllInPeriod() {
// given
final Long teamPlaceId = 3L;
final int year = 2023;
final int month = 7;

// when
final SchedulesResponse schedulesResponse = scheduleService.findScheduleIn(teamPlaceId, year, month);
final List<ScheduleResponse> scheduleResponses = schedulesResponse.schedules();

//then
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(scheduleResponses).hasSize(3);
softly.assertThat(scheduleResponses.get(0).title()).isEqualTo("3번 팀플 B");
softly.assertThat(scheduleResponses.get(1).title()).isEqualTo("3번 팀플 C");
softly.assertThat(scheduleResponses.get(2).title()).isEqualTo("3번 팀플 D");
});
}

@Test
@DisplayName("팀플레이스에서 일정이 없는 기간 내 일정들을 조회한다.")
void findAllInPeriodWith0Schedule() {
// given
final Long teamPlaceId = 3L;
final int year = 2023;
final int month = 5;

// when
final SchedulesResponse schedulesResponse = scheduleService.findScheduleIn(teamPlaceId, year, month);
final List<ScheduleResponse> scheduleResponses = schedulesResponse.schedules();

//then
assertThat(scheduleResponses).hasSize(0);
}

}

@Nested
Expand All @@ -93,7 +134,7 @@ void success() {
final Long registeredId = scheduleService.register(request, teamPlaceId);

// then
Assertions.assertThat(registeredId).isNotNull();
assertThat(registeredId).isNotNull();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package team.teamby.teambyteam.schedule.domain;

import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.jdbc.Sql;

import java.time.LocalDateTime;
import java.util.List;


@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Sql(value = {"/h2-reset-pk.sql", "/h2-data.sql"})
class ScheduleRepositoryTest {

@Autowired
private ScheduleRepository scheduleRepository;

@Test
@DisplayName("특정 기간 내 팀플레이스 일정을 조회한다.")
void findTeamPlaceScheduleInRange() {
// given
final Long teamPlaceId = 3L;
final LocalDateTime firstDateTime = LocalDateTime.of(2023, 7, 1, 0, 0);
final LocalDateTime endDateTime = LocalDateTime.of(2023, 7, 31, 23, 59);

// when
final List<Schedule> schedules = scheduleRepository.findAllByTeamPlaceInPeriod(teamPlaceId, firstDateTime, endDateTime);

//then
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(schedules).hasSize(3);
softly.assertThat(schedules.get(0).getTitle().getValue()).isEqualTo("3번 팀플 B");
softly.assertThat(schedules.get(1).getTitle().getValue()).isEqualTo("3번 팀플 C");
softly.assertThat(schedules.get(2).getTitle().getValue()).isEqualTo("3번 팀플 D");
});
}
}
6 changes: 6 additions & 0 deletions backend/src/test/resources/h2-data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALU
INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('2번 팀플 N일 일정', 2, '2023-07-14 10:00:00' ,'2023-07-17 12:00:00');
INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('2번 팀플 과거 일정', 2, TIMESTAMPADD(DAY, -2, CURRENT_TIMESTAMP()), TIMESTAMPADD(DAY, -1, CURRENT_TIMESTAMP()));
INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('2번 팀플 미래 일정', 2, TIMESTAMPADD(DAY, 3, CURRENT_TIMESTAMP()), TIMESTAMPADD(DAY, 5, CURRENT_TIMESTAMP()));

INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('3번 팀플 A', 3, '2023-06-14 10:00:00' ,'2023-06-17 12:00:00');
INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('3번 팀플 B', 3, '2023-06-25 10:00:00' ,'2023-07-02 12:00:00');
INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('3번 팀플 C', 3, '2023-07-01 10:00:00' ,'2023-07-01 12:00:00');
INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('3번 팀플 D', 3, '2023-07-29 10:00:00' ,'2023-08-30 12:00:00');
INSERT INTO schedule (title, team_place_id, start_date_time, end_date_time) VALUES ('3번 팀플 E', 3, '2023-08-14 10:00:00' ,'2023-08-17 12:00:00');