diff --git a/README.md b/README.md index 7c7b3324bd..fd820a31e5 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [x] 예약이 있는 테마를 삭제 요청시 에러 - [ ] 사용자 예약 기능 추가 +- [x] 인기 테마 기능 추가 # API 명세 diff --git a/src/main/java/roomescape/controller/ThemeController.java b/src/main/java/roomescape/controller/ThemeController.java index bc38a24576..dbb0cb2520 100644 --- a/src/main/java/roomescape/controller/ThemeController.java +++ b/src/main/java/roomescape/controller/ThemeController.java @@ -1,6 +1,7 @@ package roomescape.controller; import java.net.URI; +import java.time.LocalDate; import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -9,6 +10,7 @@ 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 org.springframework.web.bind.annotation.RestController; import roomescape.dto.ThemeRequest; import roomescape.dto.ThemeResponse; @@ -29,6 +31,13 @@ public List findAll() { return themeService.findAll(); } + @GetMapping("/ranking") + public List findAndOrderByPopularity(@RequestParam LocalDate start, + @RequestParam LocalDate end, + @RequestParam int count) { + return themeService.findAndOrderByPopularity(start, end, count); + } + @PostMapping public ResponseEntity save(@RequestBody ThemeRequest themeRequest) { ThemeResponse saved = themeService.save(themeRequest); diff --git a/src/main/java/roomescape/controller/UserController.java b/src/main/java/roomescape/controller/UserController.java index 39d95139d3..86ecc0f8ce 100644 --- a/src/main/java/roomescape/controller/UserController.java +++ b/src/main/java/roomescape/controller/UserController.java @@ -9,4 +9,9 @@ public class UserController { public String reservationPage() { return "reservation"; } + + @GetMapping("/") + public String bestThemePage() { + return "index"; + } } diff --git a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java index 7d44bd86be..497b82c526 100644 --- a/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java +++ b/src/main/java/roomescape/repository/JdbcTemplateThemeRepository.java @@ -1,9 +1,11 @@ package roomescape.repository; import java.sql.PreparedStatement; +import java.time.LocalDate; import java.util.List; import java.util.Optional; 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; @@ -12,6 +14,14 @@ @Repository public class JdbcTemplateThemeRepository implements ThemeRepository { private final JdbcTemplate jdbcTemplate; + private RowMapper themeRowMapper = (rs, rowNum) -> { + long id = rs.getLong("id"); + String name = rs.getString("name"); + String description = rs.getString("description"); + String thumbnail = rs.getString("thumbnail"); + return new Theme(id, name, description, thumbnail); + }; + ; public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -19,24 +29,20 @@ public JdbcTemplateThemeRepository(JdbcTemplate jdbcTemplate) { @Override public List findAll() { - return jdbcTemplate.query("select ID, NAME, DESCRIPTION, THUMBNAIL from THEME", (rs, rowNum) -> { - long id = rs.getLong(1); - String name = rs.getString(2); - String description = rs.getString(3); - String thumbnail = rs.getString(4); - return new Theme(id, name, description, thumbnail); - }); + return jdbcTemplate.query("select ID, NAME, DESCRIPTION, THUMBNAIL from THEME", themeRowMapper); + } + + @Override + public List findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { + return jdbcTemplate.query( + "select th.*, count(*) as count from theme th join reservation r on r.theme_id = th.id where PARSEDATETIME(r.date,'yyyy-MM-dd') >= PARSEDATETIME(?,'yyyy-MM-dd') and PARSEDATETIME(r.date,'yyyy-MM-dd') <= PARSEDATETIME(?,'yyyy-MM-dd') group by th.id order by count desc limit ?", + themeRowMapper, start, end, count); } @Override public Optional findById(long id) { List themes = jdbcTemplate.query("select id, name, description, thumbnail from theme where id = ?", - (rs, rowNum) -> { - String name = rs.getString("name"); - String description = rs.getString("description"); - String thumbnail = rs.getString("thumbnail"); - return new Theme(id, name, description, thumbnail); - }, id); + themeRowMapper, id); return themes.stream().findFirst(); } diff --git a/src/main/java/roomescape/repository/ThemeRepository.java b/src/main/java/roomescape/repository/ThemeRepository.java index a43cb24851..f9cdad4015 100644 --- a/src/main/java/roomescape/repository/ThemeRepository.java +++ b/src/main/java/roomescape/repository/ThemeRepository.java @@ -1,5 +1,6 @@ package roomescape.repository; +import java.time.LocalDate; import java.util.List; import java.util.Optional; import roomescape.domain.Theme; @@ -7,6 +8,8 @@ public interface ThemeRepository { List findAll(); + List findAndOrderByPopularity(LocalDate start, LocalDate end, int count); + Optional findById(long id); Theme save(Theme theme); diff --git a/src/main/java/roomescape/service/ThemeService.java b/src/main/java/roomescape/service/ThemeService.java index 4daac439e3..d06634420a 100644 --- a/src/main/java/roomescape/service/ThemeService.java +++ b/src/main/java/roomescape/service/ThemeService.java @@ -1,5 +1,6 @@ package roomescape.service; +import java.time.LocalDate; import java.util.List; import org.springframework.stereotype.Service; import roomescape.domain.Theme; @@ -42,6 +43,12 @@ public List findAll() { .toList(); } + public List findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { + return themeRepository.findAndOrderByPopularity(start, end, count).stream() + .map(this::toResponse) + .toList(); + } + public void delete(long id) { //todo : 변수명 고민 boolean invalidDelete = reservationRepository.findAll().stream() diff --git a/src/main/resources/static/js/ranking.js b/src/main/resources/static/js/ranking.js index dee05edf0b..c4b20948b1 100644 --- a/src/main/resources/static/js/ranking.js +++ b/src/main/resources/static/js/ranking.js @@ -1,25 +1,41 @@ document.addEventListener('DOMContentLoaded', () => { - /* - TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 - */ - requestRead('/') // 인기 테마 목록 조회 API endpoint - .then(render) - .catch(error => console.error('Error fetching times:', error)); + /* + TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 + */ + const today = new Date(); + let startDate = formatDate(minusDay(today, 7)); + let endDate = formatDate(minusDay(today, 1)); + const count = 10; + const endpoint = `/themes/ranking?start=${startDate}&end=${endDate}&count=${count}`; + requestRead(endpoint) // 인기 테마 목록 조회 API endpoint + .then(render) + .catch(error => console.error('Error fetching times:', error)); }); +function minusDay(date, minusValue) { + return new Date(new Date(date).setDate(date.getDate() - minusValue)); +} + +function formatDate(date) { + const year = date.getFullYear(); + const month = ('0' + (date.getMonth() + 1)).slice(-2); + const day = ('0' + date.getDate()).slice(-2); + return year + '-' + month + '-' + day; +} + function render(data) { - const container = document.getElementById('theme-ranking'); - - /* - TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 후 렌더링 - response 명세에 맞춰 name, thumbnail, description 값 설정 - */ - data.forEach(theme => { - const name = ''; - const thumbnail = ''; - const description = ''; - - const htmlContent = ` + const container = document.getElementById('theme-ranking'); + + /* + TODO: [3단계] 인기 테마 - 인기 테마 목록 조회 API 호출 후 렌더링 + response 명세에 맞춰 name, thumbnail, description 값 설정 + */ + data.forEach(theme => { + const name = theme.name; + const thumbnail = theme.thumbnail; + const description = theme.description; + + const htmlContent = ` ${name}
${name}
@@ -27,18 +43,18 @@ function render(data) {
`; - const div = document.createElement('li'); - div.className = 'media my-4'; - div.innerHTML = htmlContent; + const div = document.createElement('li'); + div.className = 'media my-4'; + div.innerHTML = htmlContent; - container.appendChild(div); - }) + container.appendChild(div); + }) } function requestRead(endpoint) { - return fetch(endpoint) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); + return fetch(endpoint) + .then(response => { + if (response.status === 200) return response.json(); + throw new Error('Read failed'); + }); } diff --git a/src/test/java/roomescape/repository/CollectionThemeRepository.java b/src/test/java/roomescape/repository/CollectionThemeRepository.java index d234bb8f5d..2774877fc3 100644 --- a/src/test/java/roomescape/repository/CollectionThemeRepository.java +++ b/src/test/java/roomescape/repository/CollectionThemeRepository.java @@ -1,5 +1,7 @@ package roomescape.repository; + +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -21,6 +23,11 @@ public List findAll() { return new ArrayList<>(themes); } + @Override + public List findAndOrderByPopularity(LocalDate start, LocalDate end, int count) { + return null; + } + @Override public Optional findById(long id) { return themes.stream()