Skip to content

Commit 6bcfa16

Browse files
Orders page functionality for various roles (#152)
* Fixed issues with ID and Table definitions * Create structure for orders subpage * Create structure for orders subpage * Fixed Buy now issues * Fixed typo * Fixed state passing * Fixed state passing * Cosmetic changes * Cosmetic changes * OrderPage revamp * Introduce different colors for user roles (on avatars), cosmetic changes * Implement parametrized order fetching (should be tested) * Implement all functionality regarding Orders, cosmetic changes still needed * Implement generic dialog component for decision validation * Fix enum mapping * Include items fetching when viewing orders * Implement utility endpoint for order items fetching, fix authorization for order fetching * Fix test * Tried aligning mock functions to the updated OrderRepository, but failed... * Fixed mockito mocks --------- Co-authored-by: Adam Kopeć <[email protected]>
1 parent 3f4bc00 commit 6bcfa16

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1660
-673
lines changed

FlowerShopBackend/src/main/java/pw/se2/flowershopbackend/controllers/OrderController.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,22 @@
88
import org.slf4j.LoggerFactory;
99
import org.springframework.data.domain.PageRequest;
1010
import org.springframework.data.domain.Pageable;
11+
import org.springframework.data.domain.Sort;
1112
import org.springframework.http.HttpStatus;
1213
import org.springframework.http.MediaType;
1314
import org.springframework.http.ResponseEntity;
1415
import org.springframework.web.bind.annotation.*;
1516
import org.springframework.web.server.ResponseStatusException;
17+
import pw.se2.flowershopbackend.models.BasketItem;
1618
import pw.se2.flowershopbackend.models.Order;
1719
import pw.se2.flowershopbackend.models.User;
18-
import pw.se2.flowershopbackend.services.OrderProductService;
1920
import pw.se2.flowershopbackend.services.OrderService;
2021
import pw.se2.flowershopbackend.services.ProductService;
21-
import pw.se2.flowershopbackend.web.OrderDto;
22-
import pw.se2.flowershopbackend.web.OrderStatusChangeDto;
22+
import pw.se2.flowershopbackend.web.*;
23+
2324
import java.util.Collection;
25+
import java.util.List;
2426
import java.util.UUID;
25-
import pw.se2.flowershopbackend.web.OrderCreationDto;
2627

2728
@Tag(name = "Orders")
2829
@RestController
@@ -42,13 +43,17 @@ public OrderController(OrderService orderService, ProductService productService)
4243

4344
@GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
4445
@Operation(summary = "Fetch Orders", description = "Orders will be returned in descending order by date created.")
45-
public ResponseEntity<Collection<OrderDto>> fetchOrders(@Parameter(name = "Page number", description = "The number of the page to be displayed")
46-
@RequestParam(defaultValue = "0") int page,
47-
@Parameter(name = "Maximum number of elements on page", description = "The number of elements per page that will not be exceeded")
48-
@RequestParam(defaultValue = "30") int maxPerPage) {
46+
public ResponseEntity<Collection<OrderDto>> fetchOrders(
47+
@Parameter(name = "Order type", description = "A comma separated string containing valid order statuses: " +
48+
"pending, accepted, declined, delivered. If not specified, fetch all orders according to user type.")
49+
@RequestParam(required = false) String statuses,
50+
@Parameter(name = "Page number", description = "The number of the page to be displayed")
51+
@RequestParam(defaultValue = "0") int page,
52+
@Parameter(name = "Maximum number of elements on page", description = "The number of elements per page that will not be exceeded")
53+
@RequestParam(defaultValue = "30") int maxPerPage) {
4954
User user = User.getAuthenticated();
50-
Pageable paging = PageRequest.of(page, maxPerPage);
51-
Collection<Order> orders = orderService.getOrdersFor(user, paging).getContent();
55+
Pageable paging = PageRequest.of(page, maxPerPage, Sort.by("dateCreated").descending());
56+
Collection<Order> orders = orderService.getOrdersFor(user, paging, statuses).getContent();
5257
return ResponseEntity.status(HttpStatus.OK).body(orders.stream().map(OrderDto::valueFrom).toList());
5358
}
5459

@@ -102,4 +107,15 @@ public void modifyOrder(@PathVariable UUID orderId, @RequestBody OrderCreationDt
102107
}
103108
orderService.validateAndSave(order);
104109
}
110+
111+
@GetMapping(path = "/{orderId}/items", produces = MediaType.APPLICATION_JSON_VALUE)
112+
@Operation(summary = "(UTILITY ENDPOINT) Fetch items from a specific Order")
113+
public ResponseEntity<List<BasketDto>> fetchOrderItems(@PathVariable UUID orderId) {
114+
User user = User.getAuthenticated();
115+
Order order = orderService.getOrderByIdAuth(orderId, user);
116+
List<BasketItem> basketItemList = order.getOrderProducts().stream()
117+
.map((orderProduct) -> new BasketItemCreationDto(orderProduct.getProduct().getId(), Math.toIntExact(orderProduct.getQuantity()))
118+
.convertToModel(productService)).toList();
119+
return ResponseEntity.status(HttpStatus.OK).body(basketItemList.stream().map(BasketDto::valueFrom).toList());
120+
}
105121
}

FlowerShopBackend/src/main/java/pw/se2/flowershopbackend/dao/OrderRepository.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@
77
import pw.se2.flowershopbackend.models.Order;
88
import pw.se2.flowershopbackend.models.User;
99

10+
import java.util.List;
1011
import java.util.UUID;
1112

1213
@Repository
1314
public interface OrderRepository extends JpaRepository<Order, UUID> {
1415
Page<Order> findByClient(User client, Pageable pageable);
15-
16+
Page<Order> findByClientAndStatusInOrStatusIsNull(User client, Pageable pageable, List<Order.Status> statuses);
17+
Page<Order> findByClientAndStatusIn(User client, Pageable pageable, List<Order.Status> statuses);
1618
Page<Order> findByDeliveryMan(User deliveryMan, Pageable pageable);
19+
Page<Order> findByDeliveryManAndStatusInOrStatusIsNull(User deliveryMan, Pageable pageable, List<Order.Status> statuses);
20+
Page<Order> findByDeliveryManAndStatusIn(User deliveryMan, Pageable pageable, List<Order.Status> statuses);
21+
Page<Order> findAllByStatusInOrStatusIsNull(Pageable pageable, List<Order.Status> status);
22+
Page<Order> findAllByStatusIn(Pageable pageable, List<Order.Status> statuses);
1723
}

FlowerShopBackend/src/main/java/pw/se2/flowershopbackend/services/OrderService.java

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
import org.springframework.web.server.ResponseStatusException;
1010
import pw.se2.flowershopbackend.dao.OrderRepository;
1111
import pw.se2.flowershopbackend.models.Order;
12+
import pw.se2.flowershopbackend.models.Product;
1213
import pw.se2.flowershopbackend.models.User;
1314

14-
import java.util.Optional;
15-
import java.util.UUID;
15+
import java.util.*;
1616

1717
@Service
1818
public class OrderService {
@@ -29,14 +29,54 @@ public OrderService(OrderRepository orderRepository, UserService userService, Al
2929
this.orderProductService = orderProductService;
3030
}
3131

32-
public Page<Order> getOrdersFor(User user, Pageable paging) {
33-
if (user.getRole() == User.Roles.Client) {
34-
return orderRepository.findByClient(user, paging);
35-
} else if (user.getRole() == User.Roles.DeliveryMan) {
36-
return orderRepository.findByDeliveryMan(user, paging);
37-
} else {
38-
log.error("User not authorized to perform this action.");
39-
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User not authorized to perform this action.");
32+
public Page<Order> getOrdersFor(User user, Pageable paging, String statusesString) {
33+
boolean fetchAll = false;
34+
boolean hasPending = false;
35+
if (statusesString == null)
36+
fetchAll = true;
37+
else if (statusesString.contains("pending"))
38+
hasPending = true;
39+
List<Order.Status> statusList = getStatusesFrom(statusesString);
40+
switch(user.getRole()){
41+
case Client -> {
42+
if (fetchAll || hasPending)
43+
return orderRepository.findByClientAndStatusInOrStatusIsNull(user, paging, statusList);
44+
else
45+
return orderRepository.findByClientAndStatusIn(user, paging, statusList);
46+
} case DeliveryMan -> {
47+
if (fetchAll || hasPending)
48+
return orderRepository.findByDeliveryManAndStatusInOrStatusIsNull(user, paging, statusList);
49+
else
50+
return orderRepository.findByDeliveryManAndStatusIn(user, paging, statusList);
51+
} case Employee -> {
52+
if (fetchAll || hasPending)
53+
return orderRepository.findAllByStatusInOrStatusIsNull(paging, statusList);
54+
else
55+
return orderRepository.findAllByStatusIn(paging, statusList);
56+
} default -> {
57+
log.error("User not authorized to perform this action.");
58+
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User not authorized to perform this action.");
59+
}
60+
}
61+
}
62+
63+
private List<Order.Status> getStatusesFrom(String statusesString) {
64+
// If null, return null
65+
if (statusesString == null) {
66+
return Arrays.asList(Order.Status.Accepted, Order.Status.Delivered, Order.Status.Declined, null);
67+
}
68+
// Else, map the strings to Order.Status elements and remove "pending" var
69+
List<String> statusStringList = new ArrayList<>(Arrays.stream(statusesString.split(","))
70+
.map((statusString) -> statusString.substring(0, 1).toUpperCase() + statusString.substring(1))
71+
.toList());
72+
int pendingIndex = statusStringList.indexOf("Pending");
73+
if (pendingIndex == -1)
74+
return statusStringList.stream().map(Order.Status::valueOf).toList();
75+
else {
76+
if (statusStringList.remove(pendingIndex).equals("Pending")) {
77+
return statusStringList.stream().map(Order.Status::valueOf).toList();
78+
} else
79+
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Server was not able to process the query parameter.");
4080
}
4181
}
4282

@@ -45,7 +85,7 @@ public Order getOrderByIdAuth(UUID orderID, User user) {
4585
if (optionalOrder.isPresent()) {
4686
Order order = optionalOrder.get();
4787
// Verify the user is authorized to fetch this order
48-
if (order.getClient().getId() == user.getId() || (order.getDeliveryMan() != null && order.getDeliveryMan().getId() == user.getId()) || user.getRole() == User.Roles.Employee) {
88+
if (order.getClient().getId().equals(user.getId()) || (order.getDeliveryMan() != null && (order.getDeliveryMan().getId().equals(user.getId()))) || (user.getRole().equals(User.Roles.Employee))) {
4989
return order;
5090
} else {
5191
log.error("User not authorized to perform this action.");

FlowerShopBackend/src/main/java/pw/se2/flowershopbackend/web/OrderDto.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import java.util.List;
1010
import java.util.UUID;
1111

12-
public record OrderDto(UUID orderId, AddressDto deliveryAddress, Date dateCreated, List<OrderProductDto> items) {
12+
public record OrderDto(UUID orderId, AddressDto deliveryAddress, Date dateCreated, List<OrderProductDto> items, Order.Status status) {
1313
public static OrderDto valueFrom(Order order) {
1414
AddressDto address;
1515
try {
@@ -19,6 +19,6 @@ public static OrderDto valueFrom(Order order) {
1919
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Corrupted data in database.");
2020
}
2121
List<OrderProductDto> items = order.getOrderProducts().stream().map(OrderProductDto::valueFrom).toList();
22-
return new OrderDto(order.getId(), address, order.getDateCreated(), items);
22+
return new OrderDto(order.getId(), address, order.getDateCreated(), items, order.getStatus());
2323
}
2424
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Database
22
spring.datasource.url=jdbc:mysql://localhost:3306/flowershop?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true&sessionVariables=sql_mode='NO_ENGINE_SUBSTITUTION'&jdbcCompliantTruncation=false
3-
spring.datasource.username=backend
4-
spring.datasource.password=MysqL12!@
3+
spring.datasource.username=root
4+
spring.datasource.password=root
55
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL8Dialect

FlowerShopBackend/src/test/java/pw/se2/flowershopbackend/OrderFetchingApiTests.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import java.util.*;
3535

3636
import static org.hamcrest.Matchers.hasSize;
37+
import static org.mockito.ArgumentMatchers.any;
38+
import static org.mockito.ArgumentMatchers.eq;
3739
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3840
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
3941
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -137,11 +139,17 @@ public void setUp() {
137139

138140
Mockito.when(orderRepository.findById(order.getId()))
139141
.thenReturn(Optional.of(order));
140-
141-
Mockito.when(orderRepository.findByClient(user, PageRequest.of(0, 30)))
142+
// TODO: Due to the changes in repository, the matcher and mock functions need to be aligned for tests to pass
143+
Mockito.when(orderRepository.findByClient(eq(user), any(PageRequest.class)))
144+
.thenReturn(mockPage);
145+
Mockito.when(orderRepository.findByClientAndStatusInOrStatusIsNull(eq(user), any(PageRequest.class), any(List.class)))
142146
.thenReturn(mockPage);
143-
Mockito.when(orderRepository.findByDeliveryMan(deliveryMan, PageRequest.of(0, 30)))
147+
Mockito.when(orderRepository.findByDeliveryMan(eq(deliveryMan), any(PageRequest.class)))
144148
.thenReturn(new PageImpl<>(List.of()));
149+
Mockito.when(orderRepository.findByDeliveryManAndStatusInOrStatusIsNull(eq(deliveryMan), any(PageRequest.class), any(List.class)))
150+
.thenReturn(new PageImpl<>(List.of()));
151+
Mockito.when(orderRepository.findAllByStatusInOrStatusIsNull(any(PageRequest.class), any(List.class)))
152+
.thenReturn(mockPage);
145153
}
146154

147155
@Test
@@ -162,7 +170,8 @@ public void tryFetchingAllOrdersForEmployee() throws Exception {
162170
));
163171
mvc.perform(get("/api/orders")
164172
.accept(MediaType.APPLICATION_JSON))
165-
.andExpect(status().isForbidden());
173+
.andExpect(status().is2xxSuccessful())
174+
.andExpect(jsonPath("$", hasSize(1)));
166175
}
167176

168177
@Test
@@ -174,7 +183,7 @@ public void tryFetchingAllOrdersForDeliveryMan() throws Exception {
174183
.accept(MediaType.APPLICATION_JSON))
175184
.andExpect(status().is2xxSuccessful())
176185
.andExpect(jsonPath("$", hasSize(0)));
177-
Mockito.when(orderRepository.findByDeliveryMan(deliveryMan, PageRequest.of(0, 30)))
186+
Mockito.when(orderRepository.findByDeliveryManAndStatusInOrStatusIsNull(eq(deliveryMan), any(PageRequest.class), any(List.class)))
178187
.thenReturn(mockPage);
179188
mvc.perform(get("/api/orders")
180189
.accept(MediaType.APPLICATION_JSON))

flower-shop-frontend/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
REACT_APP_LOCAL_DEV_URL='https://pw-flowershop.azurewebsites.net'
1+
REACT_APP_LOCAL_DEV_URL='http://localhost:8080'
22

flower-shop-frontend/.eslintrc.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module.exports = {
55
node: true,
66
jest: true,
77
},
8-
ignorePatterns: ["./src/static/*"],
8+
ignorePatterns: ["./src/static/*", './public/*'],
99
parser: "@babel/eslint-parser",
1010
parserOptions: {
1111
ecmaVersion: "latest",
@@ -27,7 +27,7 @@ module.exports = {
2727
"jest",
2828
"promise",
2929
"unicorn",
30-
"react-hooks"
30+
"react-hooks",
3131
],
3232
extends: [
3333
"eslint:recommended",
@@ -153,7 +153,9 @@ module.exports = {
153153
"@typescript-eslint/no-misused-promises": "warn",
154154
"@typescript-eslint/no-unused-vars": "warn",
155155
"no-promise-executor-return": "warn",
156-
"import/order": "warn"
156+
"import/order": "warn",
157+
"no-param-reassign": "off",
158+
"unicorn/no-null": "warn"
157159
},
158160
},
159161
],

flower-shop-frontend/.lintstagedrc.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
{
2-
"*.ts": [
2+
"*.{ts,tsx}": [
33
"prettier --write",
44
"eslint"
55
],
66
"*.html": [
7-
"eslint",
87
"prettier --write"
98
],
109
"*.scss": "prettier --write"

flower-shop-frontend/.prettierrc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,11 @@
33
"parser": "typescript",
44
"trailingComma": "none",
55
"singleQuote": true,
6-
"printWidth": 120
6+
"printWidth": 120,
7+
"overrides": [{
8+
"files": "*.html",
9+
"options":{
10+
"parser": "html"
11+
}
12+
}]
713
}

0 commit comments

Comments
 (0)