Fakultas Ilmu Komputer UI

Commit 728b4c67 authored by MUHAMMAD AAQIL ABDULLAH's avatar MUHAMMAD AAQIL ABDULLAH
Browse files

Implement genres

parent 89fad226
......@@ -44,8 +44,8 @@ public class AdminController {
return "successBan";
}
@GetMapping({"/register-admin"})
public String registerForAdmin(Model model){
@GetMapping({"/register-admin", "/register-curator"})
public String registerWithRole(Model model){
model.addAttribute("form", new RegisterForm());
return "registerAdmin";
}
......@@ -56,6 +56,12 @@ public class AdminController {
return ResponseEntity.ok(admin.getUsername());
}
@PostMapping({ "/register-curator"})
public ResponseEntity<String> registerCurator(@RequestBody RegisterForm user) {
User curator = userService.createUser(user.getEmail(), user.getUsername(), user.getPassword(), "CURATOR");
return ResponseEntity.ok(curator.getUsername());
}
@GetMapping(path="/list-payment")
public String paymentList(Model model){
model.addAttribute("payments", paymentService.getAllPayments());
......@@ -69,4 +75,6 @@ public class AdminController {
}
}
......@@ -56,7 +56,7 @@ public class BaseController {
List<Articles> allArticles = articleService.getAllArticles();
model.addAttribute("all_art",allArticles);
try{
model.addAttribute("user" , userService.getUser());
model.addAttribute("user" , userService.getUserByName(principal.getName()));
}
catch(Exception e){
logger.info("Not logged in.");
......
......@@ -34,7 +34,7 @@ public class CartController {
Cart cart = cartService.findCartByUsername(principal.getName());
cartService.updateCart(cart);
model.addAttribute("cart", cart);
model.addAttribute("bookorders", cartService.getAllBookOrders(userService.getUser()));
model.addAttribute("bookorders", cartService.getAllBookOrders(userService.getUserByName(principal.getName())));
return "cart";
}
......@@ -73,7 +73,7 @@ public class CartController {
User user = userService.getUserByName(principal.getName());
cartService.updateCart(cart);
model.addAttribute("cart", cart);
model.addAttribute("bookorders", cartService.getAllBookOrders(userService.getUser()));
model.addAttribute("bookorders", cartService.getAllBookOrders(userService.getUserByName(principal.getName())));
model.addAttribute("payments", userService.getPaymentMethods(user));
return "purchase";
}
......
......@@ -2,6 +2,8 @@ package id.ac.ui.cs.advprog.landiandfriends.controller;
import id.ac.ui.cs.advprog.landiandfriends.forms.CreateArticleForm;
import id.ac.ui.cs.advprog.landiandfriends.forms.CreateBookForm;
import id.ac.ui.cs.advprog.landiandfriends.forms.CreateGenreForm;
import id.ac.ui.cs.advprog.landiandfriends.model.Genre;
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.GenreServiceImpl;
......@@ -122,7 +124,28 @@ public class CuratorController {
}
@GetMapping(path={"/genre-list/{page}", "/genre-list"})
public String genreList(Model model, @PathVariable(required = false) Integer page){
if(page == null) page = 0;
model.addAttribute("genres", genreService.getGenrePage(page));
model.addAttribute("page", page);
return "genre-list";
}
@PostMapping(path={"/create-genre"})
public ResponseEntity<String> createGenre(@RequestBody CreateGenreForm form) {
Genre genre = genreService.createGenre(form.getName());
return ResponseEntity.ok().body(genre.getName());
}
@PostMapping(path={"/remove-genre/{id}"})
public ResponseEntity<String> removeGenre(@PathVariable String id) {
var genre = genreService.removeGenre(id);
if(genre != null){
return ResponseEntity.ok().body(genre.getName());
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Genre not found.");
}
}
......@@ -16,6 +16,8 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
@Controller
......@@ -71,8 +73,8 @@ public class ProfileController {
}
@GetMapping(path="/profile")
public String profile(Model model){
model.addAttribute("user", userService.getUser());
public String profile(Principal principal, Model model){
model.addAttribute("user", userService.getUserByName(principal.getName()));
return "profile";
}
......@@ -89,8 +91,8 @@ public class ProfileController {
}
@PostMapping(path="/edit-profile")
public String editProfile(@RequestBody EditProfileForm form){
userService.updateInfo(form.getUsername());
public String editProfile(Principal principal, @RequestBody EditProfileForm form){
userService.updateInfo(principal.getName(),form.getUsername());
return "redirect:/profile";
}
}
......
......@@ -5,8 +5,7 @@ import lombok.Generated;
@Generated
@Data
public class RegisterAdmin{
private String email;
private String password;
private String username;
public class CreateGenreForm {
private String name;
}
\ No newline at end of file
......@@ -26,7 +26,7 @@ public class BookProductCard implements ProductCard {
@Override
public String getInfo() {
if(book.getGenres().isEmpty()) return "";
return book.getGenres().iterator().next().toString();
return book.getGenres().iterator().next().getName();
}
@Override
......
......@@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@Repository
public interface GenreRepository extends JpaRepository<Genre,String> {
public interface GenreRepository extends JpaRepository<Genre,Long> {
@Query("SELECT g FROM Genre g WHERE g.name = ?1")
Genre findByName(String name);
......
package id.ac.ui.cs.advprog.landiandfriends.service;
import id.ac.ui.cs.advprog.landiandfriends.model.Book;
import id.ac.ui.cs.advprog.landiandfriends.model.Genre;
import org.springframework.data.domain.Page;
import java.util.List;
public interface GenreService {
List<Genre> getAllGenres();
Genre getGenreFromName(String name);
Page<Genre> getGenrePage(int page);
Genre createGenre(String name);
Genre removeGenre(String id);
}
package id.ac.ui.cs.advprog.landiandfriends.service;
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.GenreRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
......@@ -22,4 +26,28 @@ public class GenreServiceImpl implements GenreService{
public Genre getGenreFromName(String name){
return genreRepository.findByName(name);
}
@Override
public Page<Genre> getGenrePage(int page) {
return genreRepository.findAll(PageRequest.of(page, 20));
}
@Override
public Genre createGenre(String name) {
Genre genre = new Genre();
genre.setName(name);
genreRepository.save(genre);
return genre;
}
@Override
public Genre removeGenre(String id) {
long genreId = Long.parseLong(id);
Optional<Genre> genre = genreRepository.findById(genreId);
if(genre.isPresent()){
genreRepository.delete(genre.get());
return genre.get();
}
return null;
}
}
......@@ -45,13 +45,7 @@ public class PaymentServiceImpl {
public List<PaymentModel> getUserPayments(){
RyanUserDetails userDetails = (RyanUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
var user = userRepository.findByUsername(userDetails.getUsername());
List<Integer> paymentIdList = paymentRepository.findId(user.getUserId());
List<PaymentModel> payment = new ArrayList<>();
paymentIdList.forEach(
paymentId -> payment.add(paymentRepository.getPaymentFromId(paymentId))
);
return payment;
return user.getPayment() == null ? new ArrayList<>() : user.getPayment();
}
public void savePayment(PaymentModel payment) {
......
......@@ -13,6 +13,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
@Service
......@@ -23,6 +24,21 @@ public class UserServiceImpl implements UserService {
private final RoleRepository roleRepository;
@PostConstruct
private void createAdminIfNotCreated(){
User admin = userRepository.findByUsername("admin");
if(admin == null) {
admin = new User();
admin.setUsername("admin");
admin.setPassword(encodePassword("admin"));
admin.setEmail("admin");
}
Role adminRole = roleRepository.findByName("ADMIN");
admin.setRoles(adminRole == null ? new Role("ADMIN") : adminRole);
userRepository.save(admin);
}
public User createUser(String email, String username, String password) {
return createUser(email, username, password, "User");
}
......@@ -63,14 +79,8 @@ public class UserServiceImpl implements UserService {
userRepository.save(user);
}
public User getUser() {
RyanUserDetails userDetails = (RyanUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userRepository.findByEmail(userDetails.getEmail());
}
public void updateInfo(String newUsername) {
RyanUserDetails userDetails = (RyanUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
var user = userRepository.findByEmail(userDetails.getEmail());
public void updateInfo(String username, String newUsername) {
var user = userRepository.findByUsername(username);
userRepository.updateInfo(newUsername,user.getUserId());
}
......
......@@ -9,7 +9,7 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
server.error.whitelabel.enabled=false
......
$(document).ready(function() {
$(".remove-genre").submit(function (event){
event.preventDefault();
removeGenre(event.target.getAttribute("action"))
});
$(".create-genre").submit(function (event){
event.preventDefault();
createGenre()
});
});
const token = $("input[name='_csrf']").val();
const axiosConfig = {
headers:{
'X-CSRF-TOKEN': token,
'Content-Type': 'application/json'
}
}
async function createGenre(){
const submitURL = window.location.origin + "/curator/create-genre";
const response = await axios.post(submitURL,{
'name': $('#genre-name').val()
}, axiosConfig);
if(response) {
if(response.status !== 200){
alert("Something went wrong")
}
window.location = window.location.origin + "/curator/genre-list";
}
}
async function removeGenre(id) {
const submitURL = window.location.origin + "/curator/remove-genre/"+id;
const response = await axios.post(submitURL,{}, axiosConfig);
if(response) {
if(response.status !== 200){
alert("Something went wrong")
}
window.location = window.location.origin + "/curator/genre-list";
}
}
\ No newline at end of file
......@@ -3,7 +3,7 @@
lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
<title>Book List</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
......@@ -57,6 +57,7 @@
<a href ="/profile" class="sign-in-text" style="text-decoration: none; color: inherit" th:inline="text">[[${#httpServletRequest.remoteUser}]]!</a>
</div>
<div class="container">
<h2>Book List</h2>
<a href="/curator/create-book" >
<button class="btn btn-primary">Add Book</button>
</a>
......@@ -97,11 +98,11 @@
</div>
</tbody>
</table>
<a th:if="${books.hasPrevious()}" th:href="${'/curator/book-list/'+(page-1)}">
<a th:if="${books.hasPrevious()}" th:href="${'/curator/book-list/'+((page)-1)}">
<button class="btn btn-secondary">Previous Page</button>
</a>
<button th:if="${books.hasNext()}" class="btn btn-secondary">Next Page</button>
<button th:if="${books.hasNext()}" th:href="${'/curator/book-list/'+((page)+1)}" class="btn btn-secondary">Next Page</button>
</div>
......
......@@ -94,7 +94,9 @@
<div class="container">
<h2 class="subtitle" th:text="${book.getTitle()}"></h2>
<img th:src="|data:image;base64,*{book.getImg()}|" class="product-image" style="width: 150px"/>
<h3 th:text="${book.getAuthor()}"></h3>
<i th:each="genre : ${book.getGenres()}" th:text="${' - ' +genre.name}"></i>
<p class="description" th:text="${book.getDescription()}"></p>
<h4 th:text="${'Book Stock : ' + book.getStock() }"></h4>
<h4 th:text="${'Book Price : ' + book.getPrice() }"></h4>
......
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
lang="en">
<head>
<meta charset="UTF-8">
<title>Genre List</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>
body{
background-color: #F2FCFD;
background-image: url("https://i.imgur.com/BnSpO0s.png");
height: 100%;
background-position: center;
background-repeat: repeat-y;
background-size: cover;
}
.header {
padding-bottom: 10px;
display:flex;
flex-direction: row;
align-items: center;
text-align: left;
background: #448498;
color: white;
font-size: 25px;
font-family: "Helvetica",serif;
}
/* Style the search box inside the navigation bar */
.search[type=text] {
float: right;
width: 50%;
padding: 6px;
border: none;
margin-right: 2px;
padding-left: 10px;
font-size: 17px;
}
.sign-in-text{
padding-top: 5px;
min-width: 150px;
}
</style>
</head>
<body>
<div class="header">
<div style="width: 1%"></div>
<a href="/"><img src="https://i.imgur.com/cyBT3sP.png" alt="RBS" width="100" height="100" style="padding-left: 5px"></a>
<a href="/"><img src="https://i.imgur.com/aREsEE8.png" alt="Ryan's Book Store" height="100"></a>
<div style="width: 1%"></div>
<input class="search" type="text" placeholder="Search by title..">
<img src="https://i.imgur.com/WWk7FEt.png" class = "search-button" alt="search" height="40">
<img src="https://i.imgur.com/KxpWso4.png" class = "signin-button" alt="sign in" height="40" style="padding-right: 10px">
<a href ="/profile" class="sign-in-text" style="text-decoration: none; color: inherit" th:inline="text">[[${#httpServletRequest.remoteUser}]]!</a>
</div>
<div class="container">
<h2>Genre List</h2>
<p th:inline="text">Page [[${page}]]</p>
<table class="table table-striped">
<thead class="thead-light">
<tr>
<th scope="col">Genre ID</th>
<th scope="col">Name</th>
</tr>
</thead>
<tbody>
<div th:each="genre : ${genres}" th:remove="tag">
<tr>
<td th:text="${genre.genreId}">...</td>
<td th:text="${genre.name}">...</td>
<td>
<form onSubmit="return confirm('Are you sure you want to remove this genre?') "
class="remove-genre"
th:action="${genre.genreId}"
method="post">
<button class="btn btn-danger" type="submit">Remove</button>
</form>
</td>
</tr>
</div>
<tr>
<form class="create-genre" action="" th:action="@{/curator/genre-list}" method="post" >
<td>
<p>Create New Genre: </p>
</td>
<td>
<div class="form-group">
<input name="genre-name" id="genre-name" placeholder="Genre Name" class="form-control">
</div>
</td>
<td>
<button class="btn btn-success" type="submit">Confirm</button>
</td>
</form>
</tr>
</tbody>
</table>
<a th:if="${genres.hasPrevious()}" th:href="${'/curator/genre-list/'+((page)-1)}">
<button class="btn btn-secondary">Previous Page</button>
</a>
<button th:if="${genres.hasNext()}" th:href="${'/curator/genre-list/'+((page)+1)}" class="btn btn-secondary">Next Page</button>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript" th:src="@{/js/genre-list.js}"></script>
</body>
</html>
\ No newline at end of file
......@@ -93,7 +93,7 @@ class AdminControllerTest {
void whenGetRegisterAdminPage() throws Exception {
mockMvc.perform(get("/admin/register-admin"))
.andExpect(model().attributeExists("form"))
.andExpect(handler().methodName("registerForAdmin"))
.andExpect(handler().methodName("registerWithRole"))
.andExpect(view().name("registerAdmin"))
.andExpect(status().is(200));
}
......@@ -120,6 +120,39 @@ class AdminControllerTest {
}
@Test
@WithMockUser(authorities={"ADMIN"})
void whenGetRegisterCuratorPage() throws Exception {
mockMvc.perform(get("/admin/register-curator"))
.andExpect(model().attributeExists("form"))
.andExpect(handler().methodName("registerWithRole"))
.andExpect(view().name("registerAdmin"))
.andExpect(status().is(200));
}
@Test
@WithMockUser(authorities={"ADMIN"})
void whenPostRegisterCurator() throws Exception {
User curator = new User();
curator.setUsername("curator");
when(userService.createUser(any(), any(), any(), eq("CURATOR"))).thenReturn(curator);
String body = "{\"username\": \"curator\",\"email\": \"test@test.com\",\"password\": \"curator\"}";
MvcResult result = mockMvc.perform(
post("/admin/register-curator")
.content(body)
.contentType(MediaType.APPLICATION_JSON)
.with(csrf()))
.andExpect(handler().methodName("registerCurator"))
.andReturn();
assertEquals(200, result.getResponse().getStatus());
assertEquals(curator.getUsername(), result.getResponse().getContentAsString());
}
@Test
@WithMockUser(authorities={"ADMIN"})
void whenGetPaymentListPage() throws Exception {
......
......@@ -171,7 +171,7 @@ public class CartControllerTest {
.andExpect(view().name("purchase"));
verify(cartService, times(1)).findCartByUsername(username);
verify(userService, times(1)).getUserByName(username);
verify(userService, times(2)).getUserByName(username);
verify(cartService, times(1)).updateCart(any());
verify(cartService, times(1)).getAllBookOrders(any());
verify(userService, times(1)).getPaymentMethods(any());
......@@ -192,7 +192,7 @@ public class CartControllerTest {
.andExpect(view().name("purchase"));
verify(cartService, times(1)).findCartByUsername(username);
verify(userService, times(1)).getUserByName(username);
verify(userService, times(2)).getUserByName(username);
verify(cartService, times(1)).updateCart(any());
verify(cartService, times(1)).getAllBookOrders(any());
verify(userService, times(1)).getPaymentMethods(any());
......
......@@ -2,7 +2,9 @@ package id.ac.ui.cs.advprog.landiandfriends.controller;
import id.ac.ui.cs.advprog.landiandfriends.exception.InvalidStockException;
import id.ac.ui.cs.advprog.landiandfriends.forms.CreateBookForm;