Skip to content

Commit 94b2697

Browse files
committed
Implement a page with list of categories.
Fix #102
1 parent d249cc1 commit 94b2697

File tree

16 files changed

+193
-4
lines changed

16 files changed

+193
-4
lines changed

src/main/java/ru/mystamps/web/Url.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public final class Url {
5959

6060
public static final String ADD_CATEGORY_PAGE = "/category/add";
6161
public static final String INFO_CATEGORY_PAGE = "/category/{id}/{slug}";
62+
public static final String LIST_CATEGORIES_PAGE = "/category/list";
6263

6364
public static final String ADD_COUNTRY_PAGE = "/country/add";
6465
public static final String INFO_COUNTRY_PAGE = "/country/{id}/{slug}";
@@ -107,6 +108,7 @@ public static Map<String, String> asMap(boolean serveContentFromSingleHost) {
107108
map.put("ADD_IMAGE_SERIES_PAGE", ADD_IMAGE_SERIES_PAGE);
108109
map.put("ADD_CATEGORY_PAGE", ADD_CATEGORY_PAGE);
109110
map.put("INFO_CATEGORY_PAGE", INFO_CATEGORY_PAGE);
111+
map.put("LIST_CATEGORIES_PAGE", LIST_CATEGORIES_PAGE);
110112
map.put("ADD_COUNTRY_PAGE", ADD_COUNTRY_PAGE);
111113
map.put("INFO_COUNTRY_PAGE", INFO_COUNTRY_PAGE);
112114
map.put("INFO_COLLECTION_PAGE", INFO_COLLECTION_PAGE);

src/main/java/ru/mystamps/web/controller/CategoryController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,11 @@ public String showInfo(@PathVariable("id") Category category, Model model, Local
100100
return "category/info";
101101
}
102102

103+
@RequestMapping(Url.LIST_CATEGORIES_PAGE)
104+
public void list(Model model, Locale userLocale) {
105+
String lang = LocaleUtils.getLanguageOrNull(userLocale);
106+
model.addAttribute("categories", categoryService.findAllAsLinkEntities(lang));
107+
}
108+
103109
}
104110

src/main/java/ru/mystamps/web/dao/JdbcCategoryDao.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.util.Map;
2121

22+
import ru.mystamps.web.service.dto.LinkEntityDto;
2223
import ru.mystamps.web.service.dto.SelectEntityDto;
2324

2425
public interface JdbcCategoryDao {
@@ -28,4 +29,5 @@ public interface JdbcCategoryDao {
2829
long countCategoriesOfCollection(Integer collectionId);
2930
Map<String, Integer> getStatisticsOf(Integer collectionId, String lang);
3031
Iterable<SelectEntityDto> findAllAsSelectEntries(String lang);
32+
Iterable<LinkEntityDto> findAllAsLinkEntities(String lang);
3133
}

src/main/java/ru/mystamps/web/dao/impl/JdbcCategoryDaoImpl.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@
3030
import lombok.RequiredArgsConstructor;
3131

3232
import ru.mystamps.web.dao.JdbcCategoryDao;
33+
import ru.mystamps.web.service.dto.LinkEntityDto;
3334
import ru.mystamps.web.service.dto.SelectEntityDto;
3435

3536
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
3637
@RequiredArgsConstructor
3738
public class JdbcCategoryDaoImpl implements JdbcCategoryDao {
3839

40+
private static final RowMapper<LinkEntityDto> LINK_ENTITY_DTO_ROW_MAPPER =
41+
new LinkEntityDtoRowMapper();
42+
3943
private static final RowMapper<Pair<String, Integer>> NAME_AND_COUNTER_ROW_MAPPER =
4044
new StringIntegerPairRowMapper("name", "counter");
4145

@@ -62,6 +66,9 @@ public class JdbcCategoryDaoImpl implements JdbcCategoryDao {
6266
@Value("${category.find_all_categories_names_with_ids}")
6367
private String findCategoriesNamesWithIdsSql;
6468

69+
@Value("${category.find_all_categories_names_with_slug}")
70+
private String findCategoriesNamesWithSlugSql;
71+
6572
@Override
6673
public long countAll() {
6774
return jdbcTemplate.queryForObject(
@@ -137,4 +144,13 @@ public Iterable<SelectEntityDto> findAllAsSelectEntries(String lang) {
137144
return result;
138145
}
139146

147+
@Override
148+
public Iterable<LinkEntityDto> findAllAsLinkEntities(String lang) {
149+
return jdbcTemplate.query(
150+
findCategoriesNamesWithSlugSql,
151+
Collections.singletonMap("lang", lang),
152+
LINK_ENTITY_DTO_ROW_MAPPER
153+
);
154+
}
155+
140156
}

src/main/java/ru/mystamps/web/service/CategoryService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
import ru.mystamps.web.entity.Collection;
2323
import ru.mystamps.web.entity.User;
2424
import ru.mystamps.web.service.dto.AddCategoryDto;
25+
import ru.mystamps.web.service.dto.LinkEntityDto;
2526
import ru.mystamps.web.service.dto.SelectEntityDto;
2627
import ru.mystamps.web.service.dto.UrlEntityDto;
2728

2829
public interface CategoryService {
2930
UrlEntityDto add(AddCategoryDto dto, User user);
3031
Iterable<SelectEntityDto> findAll(String lang);
32+
Iterable<LinkEntityDto> findAllAsLinkEntities(String lang);
3133
long countAll();
3234
long countCategoriesOf(Collection collection);
3335
long countByName(String name);

src/main/java/ru/mystamps/web/service/CategoryServiceImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import ru.mystamps.web.entity.User;
3838
import ru.mystamps.web.dao.CategoryDao;
3939
import ru.mystamps.web.service.dto.AddCategoryDto;
40+
import ru.mystamps.web.service.dto.LinkEntityDto;
4041
import ru.mystamps.web.service.dto.SelectEntityDto;
4142
import ru.mystamps.web.service.dto.UrlEntityDto;
4243
import ru.mystamps.web.util.SlugUtils;
@@ -87,6 +88,12 @@ public Iterable<SelectEntityDto> findAll(String lang) {
8788
return jdbcCategoryDao.findAllAsSelectEntries(lang);
8889
}
8990

91+
@Override
92+
@Transactional(readOnly = true)
93+
public Iterable<LinkEntityDto> findAllAsLinkEntities(String lang) {
94+
return jdbcCategoryDao.findAllAsLinkEntities(lang);
95+
}
96+
9097
@Override
9198
@Transactional(readOnly = true)
9299
public long countAll() {

src/main/java/ru/mystamps/web/support/togglz/Features.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ public enum Features implements Feature {
5050

5151
@Label("Possibility of user to add additional images to series")
5252
@EnabledByDefault
53-
ADD_ADDITIONAL_IMAGES_TO_SERIES;
53+
ADD_ADDITIONAL_IMAGES_TO_SERIES,
54+
55+
@Label("Show link to list of categories on index page")
56+
@EnabledByDefault
57+
LIST_CATEGORIES;
5458

5559
public boolean isActive() {
5660
return FeatureContext.getFeatureManager().isActive(this);

src/main/resources/ru/mystamps/i18n/Messages.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ t_write_email = Write e-mail
3030
# site/index.html
3131
t_index_title = create your own virtual collection!
3232
t_you_may = You may
33+
t_show_categories_list = show list of categories
3334
t_in_db = In our database
3435
t_categories_amount = Amount of categories
3536
t_countries_amount = Amount of countries
@@ -116,6 +117,9 @@ t_create_category_ucfirst = Add category
116117
t_category_info = Category info
117118
t_category_just_added = Category has been added.<br />Now you could <a href="{0}" class="alert-link">proceed with creating series</a>.
118119

120+
# category/list.html
121+
t_category_list = category list
122+
119123
# country/add.html
120124
t_add_country_ucfirst = Add country
121125

src/main/resources/ru/mystamps/i18n/Messages_ru.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ t_write_email = Написать письмо
3030
# site/index.html
3131
t_index_title = создай свою виртуальную коллекцию!
3232
t_you_may = Вы можете
33+
t_show_categories_list = посмотреть список категорий
3334
t_in_db = В нашей базе
3435
t_categories_amount = Категорий
3536
t_countries_amount = Стран
@@ -116,6 +117,9 @@ t_create_category_ucfirst = Создать категорию
116117
t_category_info = Информация о категории
117118
t_category_just_added = Категория добавлена. Теперь вы можете <a href="{0}" class="alert-link">создать серию</a>.
118119

120+
# category/list.html
121+
t_category_list = список категорий
122+
119123
# country/add.html
120124
t_add_country_ucfirst = Добавить страну
121125

src/main/resources/sql/category_dao_queries.properties

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,10 @@ category.find_all_categories_names_with_ids = \
3535
, c.id \
3636
FROM categories c \
3737
ORDER BY CASE WHEN 'ru' = :lang THEN c.name_ru ELSE c.name END
38+
39+
category.find_all_categories_names_with_slug = \
40+
SELECT CASE WHEN 'ru' = :lang THEN c.name_ru ELSE c.name END AS name \
41+
, c.slug \
42+
, c.id \
43+
FROM categories c \
44+
ORDER BY CASE WHEN 'ru' = :lang THEN c.name_ru ELSE c.name END
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!DOCTYPE html>
2+
<html xmlns="http://www.w3.org/1999/xhtml"
3+
xmlns:th="http://www.thymeleaf.org"
4+
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
5+
<head>
6+
<meta charset="utf-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
9+
<title th:text="|MyStamps: #{t_category_list}|">MyStamps: category list</title>
10+
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.ico" th:href="${FAVICON_ICO}" />
11+
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" th:href="${BOOTSTRAP_CSS}" />
12+
<link rel="stylesheet" href="../../static/styles/main.css" th:href="${MAIN_CSS}" />
13+
</head>
14+
<body lang="en" th:lang="${#locale.language == 'ru' ? 'ru' : 'en'}">
15+
<div class="container-fluid">
16+
<div class="row" id="header">
17+
<div id="logo" class="col-sm-10">
18+
<a href="../site/index.html" th:href="'/'" th:text="#{t_my_stamps}">My stamps</a>
19+
</div>
20+
21+
<div id="user_bar" class="col-sm-2">
22+
<ul class="list-unstyled">
23+
<!--/*/
24+
<li sec:authorize="isAuthenticated()">
25+
<i class="glyphicon glyphicon-user"></i>
26+
<a sec:authentication="principal.user.name"
27+
href="../collection/info.html"
28+
title="Open my collection"
29+
th:title="#{t_open_my_collection}"
30+
th:href="@{${INFO_COLLECTION_PAGE}(id=${#authentication.principal.user.collection.id},slug=${#authentication.principal.user.collection.slug})}">
31+
John Doe
32+
</a>
33+
</li>
34+
/*/-->
35+
<li sec:authorize="isAnonymous()">
36+
<a href="../account/auth.html" th:href="@{${AUTHENTICATION_PAGE}}" th:text="#{t_enter}">Sign in</a>
37+
</li>
38+
<!--/*/
39+
<li sec:authorize="isAuthenticated()">
40+
<form id="LogoutForm" method="get" action="../site/index.html" class="no-margin" th:method="post" th:action="@{${LOGOUT_PAGE}}">
41+
<i class="glyphicon glyphicon-share"></i>&nbsp;<input type="submit" value="Sign out" class="btn btn-link no-padding" th:value="#{t_logout}" />
42+
</form>
43+
</li>
44+
/*/-->
45+
<li sec:authorize="isAnonymous()">
46+
<a href="../account/register.html" th:href="@{${REGISTRATION_PAGE}}" th:text="#{t_register}">Register</a>
47+
</li>
48+
</ul>
49+
</div>
50+
</div>
51+
<div class="row">
52+
<div id="content" class="col-sm-12" th:with="category_list=#{t_category_list}">
53+
<h3 th:text="${#strings.capitalize(category_list)}">
54+
Category list
55+
</h3>
56+
57+
<ul th:if="${not #lists.isEmpty(categories)}">
58+
<li th:each="category: ${categories}">
59+
<a href="../category/info.html" th:href="@{${INFO_CATEGORY_PAGE}(id=${category.id},slug=${category.slug})}" th:text="${category.name}">Animals</a>
60+
</li>
61+
</ul>
62+
</div>
63+
</div>
64+
<div class="row">
65+
<footer class="col-sm-12 text-right">
66+
<i class="glyphicon glyphicon-envelope"></i>
67+
<a href="mailto:slava.semushin@gmail.com" title="Write e-mail" th:href="|mailto:#{t_site_author_email}|" th:title="#{t_write_email}" th:text="#{t_site_author_name}">Slava Semushin</a>, 2009-2015
68+
</footer>
69+
</div>
70+
</div>
71+
72+
<!-- Placed at the end of the document so the pages load faster -->
73+
<script src="http://yandex.st/jquery/1.9.1/jquery.min.js" th:src="${JQUERY_JS}"></script>
74+
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" th:src="${BOOTSTRAP_JS}"></script>
75+
</body>
76+
</html>

src/main/webapp/WEB-INF/views/site/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@
5050
</div>
5151
<div class="row" id="content">
5252
<div class="col-sm-3">
53-
<nav sec:authorize="hasAnyAuthority('CREATE_CATEGORY', 'CREATE_COUNTRY', 'CREATE_SERIES')">
53+
<nav>
5454
<p th:text="|#{t_you_may}:|">You may</p>
5555
<ul>
56+
<li togglz:active="LIST_CATEGORIES">
57+
<a th:href="@{${LIST_CATEGORIES_PAGE}}" th:text="#{t_show_categories_list}" href="../category/list.html">show list of categories</a>
58+
</li>
5659
<li sec:authorize="hasAuthority('CREATE_SERIES')">
5760
<a th:href="@{${ADD_SERIES_PAGE}}" th:text="#{t_add_series}" href="../series/add.html">add stamp series</a>
5861
</li>

src/test/groovy/ru/mystamps/web/service/CategoryServiceImplTest.groovy

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import ru.mystamps.web.entity.Category
2626
import ru.mystamps.web.entity.Collection
2727
import ru.mystamps.web.entity.User
2828
import ru.mystamps.web.model.AddCategoryForm
29+
import ru.mystamps.web.service.dto.LinkEntityDto
2930
import ru.mystamps.web.service.dto.SelectEntityDto
3031
import ru.mystamps.web.service.dto.UrlEntityDto
3132
import ru.mystamps.web.tests.DateUtils
@@ -222,6 +223,40 @@ class CategoryServiceImplTest extends Specification {
222223
null | _
223224
}
224225

226+
//
227+
// Tests for findAllAsLinkEntities(String)
228+
//
229+
230+
def "findAllAsLinkEntities(String) should call dao"() {
231+
given:
232+
LinkEntityDto category1 = new LinkEntityDto(1, 'first-category', 'First Category')
233+
and:
234+
LinkEntityDto category2 = new LinkEntityDto(2, 'second-category', 'Second Category')
235+
and:
236+
List<LinkEntityDto> expectedCategories = [ category1, category2 ]
237+
and:
238+
jdbcCategoryDao.findAllAsLinkEntities(_ as String) >> expectedCategories
239+
when:
240+
Iterable<LinkEntityDto> resultCategories = service.findAllAsLinkEntities('fr')
241+
then:
242+
resultCategories == expectedCategories
243+
}
244+
245+
@Unroll
246+
def "findAllAsLinkEntities(String) should pass language '#expectedLanguage' to dao"(String expectedLanguage) {
247+
when:
248+
service.findAllAsLinkEntities(expectedLanguage)
249+
then:
250+
1 * jdbcCategoryDao.findAllAsLinkEntities({ String language ->
251+
assert language == expectedLanguage
252+
return true
253+
})
254+
where:
255+
expectedLanguage | _
256+
'ru' | _
257+
null | _
258+
}
259+
225260
//
226261
// Tests for countAll()
227262
//

src/test/java/ru/mystamps/web/tests/cases/WhenAdminAtIndexPage.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ public void shouldExistsLinkForAddingCategories() {
8484
.isTrue();
8585
}
8686

87+
@Test(groups = "misc", dependsOnGroups = "std")
88+
public void shouldExistsLinkForListingCategories() {
89+
assertThat(page.linkWithLabelExists(tr("t_show_categories_list")))
90+
.overridingErrorMessage("should exists link to page for listing categories")
91+
.isTrue();
92+
}
93+
8794
@Override
8895
protected void shouldHaveUserBar() {
8996
// Ignore this check because when user authenticated there is no links for login/register.

src/test/java/ru/mystamps/web/tests/cases/WhenAnonymousUserAtIndexPage.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,15 @@ public void shouldHaveStandardStructure() {
4444
}
4545

4646
@Test(groups = "misc", dependsOnGroups = "std")
47-
public void shouldAbsentWelcomeText() {
48-
assertThat(page.textPresent(tr("t_you_may"))).isFalse();
47+
public void shouldExistsWelcomeText() {
48+
assertThat(page.textPresent(tr("t_you_may"))).isTrue();
49+
}
50+
51+
@Test(groups = "misc", dependsOnGroups = "std")
52+
public void shouldExistsLinkForListingCategories() {
53+
assertThat(page.linkWithLabelExists(tr("t_show_categories_list")))
54+
.overridingErrorMessage("should exists link to page for listing categories")
55+
.isTrue();
4956
}
5057

5158
@Test(groups = "misc", dependsOnGroups = "std")

src/test/java/ru/mystamps/web/tests/cases/WhenUserAtIndexPage.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ public void linkForAddingCategoriesShouldBeAbsent() {
7777
.isFalse();
7878
}
7979

80+
@Test(groups = "misc", dependsOnGroups = "std")
81+
public void shouldExistsLinkForListingCategories() {
82+
assertThat(page.linkWithLabelExists(tr("t_show_categories_list")))
83+
.overridingErrorMessage("should exists link to page for listing categories")
84+
.isTrue();
85+
}
86+
8087
@Test(groups = "misc", dependsOnGroups = "std")
8188
public void linkForAddingCountriesShouldBeAbsent() {
8289
assertThat(page.linkWithLabelExists(tr("t_add_country")))

0 commit comments

Comments
 (0)