Fakultas Ilmu Komputer UI

Commit 89fad226 authored by MUHAMMAD AAQIL ABDULLAH's avatar MUHAMMAD AAQIL ABDULLAH
Browse files

Implement search

parent 5f52714a
package id.ac.ui.cs.advprog.landiandfriends.controller;
import id.ac.ui.cs.advprog.landiandfriends.forms.AddPaymentForm;
import id.ac.ui.cs.advprog.landiandfriends.forms.RegisterForm;
import id.ac.ui.cs.advprog.landiandfriends.model.PaymentModel;
import id.ac.ui.cs.advprog.landiandfriends.model.User;
import id.ac.ui.cs.advprog.landiandfriends.service.PaymentServiceImpl;
import id.ac.ui.cs.advprog.landiandfriends.service.UserServiceImpl;
......@@ -53,10 +51,9 @@ public class AdminController {
}
@PostMapping({ "/register-admin"})
public ResponseEntity<RegisterForm> registerAdmin(@RequestBody RegisterForm user) {
userService.createUser(user.getEmail(), user.getUsername(), user.getPassword(), "ADMIN");
return ResponseEntity.ok(user);
public ResponseEntity<String> registerAdmin(@RequestBody RegisterForm user) {
User admin = userService.createUser(user.getEmail(), user.getUsername(), user.getPassword(), "ADMIN");
return ResponseEntity.ok(admin.getUsername());
}
@GetMapping(path="/list-payment")
......
......@@ -30,8 +30,9 @@ import java.util.stream.Stream;
@RequiredArgsConstructor
public class BaseController {
private final UserServiceImpl userService;
private final BookServiceImpl bookStoreService;
private final BookServiceImpl bookService;
private final ArticleServiceImpl articleService;
private final String USERNAME_ATTR = "username";
private final Logger logger = LoggerFactory.getLogger(BaseController.class);
......@@ -40,7 +41,7 @@ public class BaseController {
model.addAttribute("products",
Stream.concat(
bookStoreService.getRandomBooks(6).stream().map(BookProductCard::new),
bookService.getRandomBooks(6).stream().map(BookProductCard::new),
articleService.getRandomArticles(6).stream().map(ArticleProductCard::new))
.collect(Collectors.toList()));
model.addAttribute(USERNAME_ATTR, (principal != null) ? principal.getName() : "");
......@@ -73,4 +74,17 @@ public class BaseController {
}
@GetMapping(path = "/search/{title}")
public String getSearch(Principal principal, Model model, @PathVariable(name = "title")String title) {
model.addAttribute(USERNAME_ATTR, (principal != null) ? principal.getName() : "");
model.addAttribute("searched",
Stream.concat(
bookService.getSearchedBooks(title)
.stream().map(BookProductCard::new),
articleService.getSearchedArticles(title)
.stream().map(ArticleProductCard::new)
).collect(Collectors.toList()));
return "home";
}
}
package id.ac.ui.cs.advprog.landiandfriends.controller;
import id.ac.ui.cs.advprog.landiandfriends.model.Book;
import id.ac.ui.cs.advprog.landiandfriends.model.productcard.BookProductCard;
import id.ac.ui.cs.advprog.landiandfriends.service.BookServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.security.Principal;
import java.util.stream.Collectors;
@Controller
@RequiredArgsConstructor
public class BookPageController {
private final BookServiceImpl bookService;
private final String USERNAME_ATTR = "username";
@GetMapping(path = "/book/{bookId}")
public String getBookPage(Principal principal, Model model, @PathVariable(name = "bookId")String bookId) {
model.addAttribute(USERNAME_ATTR, (principal != null) ? principal.getName() : "");
model.addAttribute("book", bookService.getBookById(bookId));
return "book-page";
}
@GetMapping(path = "/book/search/{bookTitle}")
public String getBookSearch(Principal principal, Model model, @PathVariable(name = "bookTitle")String booktitle) {
if (principal != null) {
model.addAttribute(USERNAME_ATTR, principal.getName());
} else {
model.addAttribute(USERNAME_ATTR, "");
model.addAttribute("username", (principal != null) ? principal.getName() : "");
Book book = bookService.getBookById(bookId);
if(book == null){
return "redirect:/";
}
model.addAttribute("searched", bookService.getSearchedBookTitles(booktitle));
model.addAttribute(USERNAME_ATTR, (principal != null) ? principal.getName() : "");
return "home";
model.addAttribute("book", book);
return "book-page";
}
}
......@@ -45,10 +45,9 @@ public class ForgotPasswordController {
String resetPasswordLink = Utility.getSiteURL(request) + "/reset_password?token=" + token;
sendEmail(email, resetPasswordLink);
model.addAttribute(MESSAGE_ATTR, "We have sent a reset password link to your email. Please check.");
} catch (UsernameNotFoundException ex) {
model.addAttribute("error", ex.getMessage());
} catch (UnsupportedEncodingException | MessagingException e) {
} catch (Exception e) {
model.addAttribute("error", "Error while sending email");
}
......@@ -106,12 +105,9 @@ public class ForgotPasswordController {
if (user == null) {
model.addAttribute(MESSAGE_ATTR, "Invalid Token");
return MESSAGE_ATTR;
} else {
userService.updatePassword(user, password);
model.addAttribute(MESSAGE_ATTR, "You have successfully changed your password.");
}
userService.updatePassword(user, password);
model.addAttribute(MESSAGE_ATTR, "You have successfully changed your password.");
return MESSAGE_ATTR;
}
}
package id.ac.ui.cs.advprog.landiandfriends.controller;
import id.ac.ui.cs.advprog.landiandfriends.service.UserServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
@Controller
@RequiredArgsConstructor
public class UserController {
private final UserServiceImpl userService;
}
......@@ -65,6 +65,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers("/admin/**").hasAnyAuthority("ADMIN")
.antMatchers("/cart/**").hasAnyAuthority("USER")
.antMatchers("/profile/**").hasAnyAuthority("USER")
.antMatchers("/payment-methods/**").hasAnyAuthority("USER")
.antMatchers("/delete-payment/**").hasAnyAuthority("USER")
.anyRequest().permitAll()
.and()
.formLogin()
......
package id.ac.ui.cs.advprog.landiandfriends.service;
import id.ac.ui.cs.advprog.landiandfriends.model.Articles;
import id.ac.ui.cs.advprog.landiandfriends.model.Book;
import id.ac.ui.cs.advprog.landiandfriends.repository.ArticleRepository;
import id.ac.ui.cs.advprog.landiandfriends.util.ImageConverter;
import lombok.RequiredArgsConstructor;
......@@ -11,6 +12,7 @@ import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
......@@ -73,6 +75,11 @@ public class ArticleServiceImpl implements ArticleService {
Collections.shuffle(articles);
return articles.subList(0, Math.min(amount, articles.size()));
}
public List<Articles> getSearchedArticles(String title){
return repo.findAll().stream().filter(article -> article.getTitle().toUpperCase().contains(title.toUpperCase())).collect(Collectors.toList());
}
}
......@@ -108,14 +108,15 @@ public class BookServiceImpl implements BookService {
Book book = bookRepository.findByBookId(bookId);
book.setStock(book.getStock()-1);
}
@Override
public void incrementBookAmount(long bookId) {
Book book = bookRepository.findByBookId(bookId);
book.setStock(book.getStock()+1);
}
public List<String> getSearchedBookTitles(String title){
return bookRepository.findAll().stream().filter(e -> e.getTitle().equals(title)).map(Book::getTitle).collect(Collectors.toList());
public List<Book> getSearchedBooks(String title){
return bookRepository.findAll().stream().filter(book -> book.getTitle().toUpperCase().contains(title.toUpperCase())).collect(Collectors.toList());
}
......
......@@ -5,9 +5,9 @@ spring.datasource.hikari.connectionTimeout=20000
spring.datasource.hikari.maximumPoolSize=5
spring.datasource.maximum-pool-size=100
spring.datasource.url=jdbc:postgresql://postgres:5432/${POSTGRES_DB}
spring.datasource.username=${POSTGRES_USER}
spring.datasource.password=${POSTGRES_PASSWORD}
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
......
$(document).ready(function() {
$("#search-form").submit(function (event){
event.preventDefault();
searchForm()
});
});
async function searchForm() {
window.location = window.location.origin + "/search/" + $("#bookTitle").val();
}
\ No newline at end of file
......@@ -71,8 +71,8 @@
<a href="/"><img src="https://i.imgur.com/aREsEE8.png" alt="Ryan's Book Store" height="100"></a>
<div style="width: 1%"></div>
<div class="forminput">
<form th:action="@{/book/search/{bookTitle} (bookTitle = bookTitle )}") method="get">
<input name="bookTitle" id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<form id="search-form" th:action="@{/book/search/}" method="get">
<input id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<button type="submit">
<img src="https://i.imgur.com/WWk7FEt.png" class = "search-button" alt="search" height="40">
</button>
......@@ -121,4 +121,8 @@
</div>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript" th:src="@{/js/search.js}"></script>
</html>
\ No newline at end of file
......@@ -74,8 +74,8 @@
<div style="width: 1%"></div>
<div style="width: 1%"></div>
<div class="forminput">
<form th:action="@{/book/search/{bookTitle} (bookTitle = bookTitle )}") method="get">
<input name="bookTitle" id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<form id="search-form" th:action="@{/book/search/}" method="get">
<input id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<button type="submit">
<img src="https://i.imgur.com/WWk7FEt.png" class = "search-button" alt="search" height="40">
</button>
......@@ -89,16 +89,22 @@
<a th:href ="${username.isBlank() ? '/login' : '/profile'}"><img src="https://i.imgur.com/KxpWso4.png" class = "signin-button" alt="sign in" height="40" style="padding-right: 10px"></a>
<a th:href ="${username.isBlank() ? '/login' : '/profile'}" class="sign-in-text" style="text-decoration: none; color: inherit" th:text="${username.isBlank()} ? 'Sign In' : ${username}"></a>
</div>
<div class="container">
<h2 class="subtitle" th:text="${book.getTitle()}"></h2>
<h3 th:text="${book.getAuthor()}"></h3>
<p class="description" th:text="${book.getDescription()}"></p>
<h4 th:text="${'Book Stock : ' + book.getStock() }"></h4>
<h4 th:text="${'Book Price : ' + book.getPrice() }"></h4>
<div class="center">
<a th:href="@{/cart/add/{id}(id=${book.getBookId()})}"><img src="https://i.imgur.com/dyGvUQh.png" height="60"> </a>
<a th:attr="onclick=|window.location.href='/add-wishlist/${book.getBookId()}'|"><img src="https://i.imgur.com/jzbUtsh.png" height="60"/></a>
</div>
</div>
<div class="container">
<h2 class="subtitle" th:text="${book.getTitle()}"></h2>
<h3 th:text="${book.getAuthor()}"></h3>
<p class="description" th:text="${book.getDescription()}"></p>
<h4 th:text="${'Book Stock : ' + book.getStock() }"></h4>
<h4 th:text="${'Book Price : ' + book.getPrice() }"></h4>
<div class="center">
<a th:href="@{/cart/add/{id}(id=${book.getBookId()})}"><img src="https://i.imgur.com/dyGvUQh.png" height="60"> </a>
<a th:attr="onclick=|window.location.href='/add-wishlist/${book.getBookId()}'|"><img src="https://i.imgur.com/jzbUtsh.png" height="60"/></a>
</div>
</div>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript" th:src="@{/js/search.js}"></script>
</html>
\ No newline at end of file
......@@ -65,12 +65,12 @@
<a href="/"><img src="https://i.imgur.com/aREsEE8.png" alt="Ryan's Book Store" height="100"></a>
<div style="width: 1%"></div>
<div class="forminput">
<form th:action="@{/book/search/{bookTitle} (bookTitle = bookTitle )}") method="get">
<input name="bookTitle" id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<button type="submit">
<img src="https://i.imgur.com/WWk7FEt.png" class = "search-button" alt="search" height="40">
</button>
</form>
<form id="search-form" th:action="@{/book/search/}" method="get">
<input id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<button type="submit">
<img src="https://i.imgur.com/WWk7FEt.png" class = "search-button" alt="search" height="40">
</button>
</form>
</div>
<div style="width: 3%"></div>
<a href="/wishlist"><img src="https://i.imgur.com/HwTZHG8.png" class = "wishlist-button" alt="wishlist" height="40"></a>
......@@ -87,7 +87,7 @@
<div th:each="product : ${products}" class="card_item">
<div class="card_inner">
<img th:src="|data:image;base64,*{product.getImg()}|" class="product-image" style="width: 150px">
<div th:text="${product.getTitle()}"class="role_name"></div>
<div th:text="${product.getTitle()}" class="role_name"></div>
<div th:inline="text" class="real_name"> by [[${product.getCreator()}]]</div>
<p th:text="${product.getInfo()}"></p>
<a th:href="${product.getLink()}" class="btn btn-primary">View</a>
......@@ -100,13 +100,26 @@
</div>
<div th:if="${searched != null} "class="container">
<h2 class="subtitle"><strong>Books:</strong></h2>
<div th:each="book : ${searched}">
<a th:href="${'/book/'+book.getBookId()}" >
<h3 th:inline="text"> [[${book.getTitle()}]]<small> by [[${book.getAuthor()}]]</small></h3>
</a>
<h2 class="subtitle"><strong>Search Results:</strong></h2>
<div class="wrapper">
<div class="cards_wrap">
<div th:each="product : ${searched}" class="card_item">
<div class="card_inner">
<img th:src="|data:image;base64,*{product.getImg()}|" class="product-image" style="width: 150px">
<div th:text="${product.getTitle()}" class="role_name"></div>
<div th:inline="text" class="real_name"> by [[${product.getCreator()}]]</div>
<p th:text="${product.getInfo()}"></p>
<a th:href="${product.getLink()}" class="btn btn-primary">View</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript" th:src="@{/js/search.js}"></script>
</html>
......@@ -64,8 +64,8 @@
<a href="/"><img src="https://i.imgur.com/aREsEE8.png" alt="Ryan's Book Store" height="100"></a>
<div style="width: 1%"></div>
<div class="forminput">
<form th:action="@{/book/search/{bookTitle} (bookTitle = bookTitle )}") method="get">
<input name="bookTitle" id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<form id="search-form" th:action="@{/book/search/}" method="get">
<input id = "bookTitle" class="search" type="text" placeholder="Search by title..">
<button type="submit">
<img src="https://i.imgur.com/WWk7FEt.png" class = "search-button" alt="search" height="40">
</button>
......@@ -79,22 +79,24 @@
<a th:href ="${username.isBlank() ? '/login' : '/profile'}"><img src="https://i.imgur.com/KxpWso4.png" class = "signin-button" alt="sign in" height="40" style="padding-right: 10px"></a>
<a th:href ="${username.isBlank() ? '/login' : '/profile'}" class="sign-in-text" style="text-decoration: none; color: inherit" th:text="${username.isBlank()} ? 'Sign In' : ${username}"></a>
</div>
<div style="width:100%">
<blockquote class="blockquote text-center">
<p th:text="${article.title}" class="mb-0"></p>
<p></p>
<footer class="blockquote-footer">Authored by <cite title="Source Title" th:text="${article.author}"> </cite></footer>
</blockquote>
<hr style="width:85%"></hr>
</div>
<div class="container">
<img th:src="${'/image/'+article.imageId}" class="product-image", style="width: 150px">
<div class="baru" ><p th:text="${article.content}"></p></div>
</div>
<div style="width:100%">
<blockquote class="blockquote text-center">
<p th:text="${article.title}" class="mb-0"></p>
<p></p>
<footer class="blockquote-footer">Authored by <cite title="Source Title" th:text="${article.author}"> </cite></footer>
</blockquote>
<hr style="width:85%"></hr>
</div>
<div class="container">
<img th:src="${'/image/'+article.imageId}" class="product-image", style="width: 150px">
<div class="baru" ><p th:text="${article.content}"></p></div>
</div>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript" th:src="@{/js/search.js}"></script>
</html>
\ No newline at end of file
......@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
......@@ -80,11 +81,68 @@ class AdminControllerTest {
@Test
@WithMockUser(authorities={"ADMIN"})
void whenSuccessValid() throws Exception {
mockMvc.perform(get("/admin/success"))
.andExpect(status().isOk())
.andExpect(handler().methodName("success"))
.andExpect(view().name("successBan")).andExpect(status().is(200));
}
@Test
@WithMockUser(authorities={"ADMIN"})
void whenGetRegisterAdminPage() throws Exception {
mockMvc.perform(get("/admin/register-admin"))
.andExpect(model().attributeExists("form"))
.andExpect(handler().methodName("registerForAdmin"))
.andExpect(view().name("registerAdmin"))
.andExpect(status().is(200));
}
@Test
@WithMockUser(authorities={"ADMIN"})
void whenPostRegisterAdmin() throws Exception {
User admin = new User();
admin.setUsername("admin");
when(userService.createUser(any(), any(), any(), eq("ADMIN"))).thenReturn(admin);
String body = "{\"username\": \"admin\",\"email\": \"test@test.com\",\"password\": \"admin\"}";
MvcResult result = mockMvc.perform(
post("/admin/register-admin")
.content(body)
.contentType(MediaType.APPLICATION_JSON)
.with(csrf()))
.andExpect(handler().methodName("registerAdmin"))
.andReturn();
assertEquals(200, result.getResponse().getStatus());
assertEquals(admin.getUsername(), result.getResponse().getContentAsString());
}
@Test
@WithMockUser(authorities={"ADMIN"})
void whenGetPaymentListPage() throws Exception {
mockMvc.perform(get("/admin/list-payment"))
.andExpect(model().attributeExists("payments"))
.andExpect(handler().methodName("paymentList"))
.andExpect(view().name("admin-payment-list"))
.andExpect(status().is(200));
}
@Test
@WithMockUser(authorities={"ADMIN"})
void whenPostDeletePayment() throws Exception {
MvcResult result = mockMvc.perform(
post("/admin/admin-delete-payment/0")
.with(csrf()))
.andExpect(handler().methodName("deletePayment"))
.andReturn();
assertEquals(302, result.getResponse().getStatus());
verify(paymentService, times(1)).deletePayment(0);
}
}
\ No newline at end of file
......@@ -101,4 +101,35 @@ class BaseControllerTest {
}
@Test
void whenOpenSearchPageShouldCallBookService() throws Exception {
String bookTitle = "string";
when(bookService.getSearchedBooks(bookTitle)).thenReturn(new ArrayList<>());
mockMvc.perform(get("/search/string"))
.andExpect(status().isOk())
.andExpect(handler().methodName("getSearch"))
.andExpect(model().attributeExists( "username", "searched"))
.andExpect(view().name("home"));
verify(bookService, times(1)).getSearchedBooks(bookTitle);
}
@Test
@WithMockUser(username="Test")
void whenOpenSearchPageSignedInShouldHaveUsername() throws Exception {
String bookTitle = "string";
when(bookService.getSearchedBooks(bookTitle)).thenReturn(new ArrayList<>());
mockMvc.perform(get("/search/string/"))
.andExpect(status().isOk())
.andExpect(handler().methodName("getSearch"))
.andExpect(model().attributeExists("username", "searched"))
.andExpect(model().attribute("username", "Test"))
.andExpect(view().name("home"));
verify(bookService, times(1)).getSearchedBooks(bookTitle);
}
}
package id.ac.ui.cs.advprog.landiandfriends.controller;
import id.ac.ui.cs.advprog.landiandfriends.model.Book;
import id.ac.ui.cs.advprog.landiandfriends.model.Genre;
import id.ac.ui.cs.advprog.landiandfriends.repository.UserRepository;
import id.ac.ui.cs.advprog.landiandfriends.service.ArticleService;
import id.ac.ui.cs.advprog.landiandfriends.service.ArticleServiceImpl;
import id.ac.ui.cs.advprog.landiandfriends.service.BookServiceImpl;
import id.ac.ui.cs.advprog.landiandfriends.service.UserServiceImpl;
......@@ -15,7 +13,6 @@ import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
......@@ -53,6 +50,18 @@ public class BookPageControllerTest {
verify(bookService, times(1)).getBookById(bookId);
}
@Test
void whenOpenBookPageBookNotFound() throws Exception {
String bookId = "1";
when(bookService.getBookById(bookId)).thenReturn(null);
mockMvc.perform(get("/book/1"))
.andExpect(status().is3xxRedirection())
.andExpect(handler().methodName("getBookPage"));
verify(bookService, times(1)).getBookById(bookId);
}
@Test
@WithMockUser(username="Test")
void whenOpenBookPageSignedInShouldHaveUsername() throws Exception {
......@@ -68,34 +77,4 @@ public class BookPageControllerTest {
verify(bookService, times(1)).getBookById(bookId);
}
@Test
void whenOpenSearchPageShouldCallBookService() throws Exception {