This project puts the module together. Logged-in customers can review a product: a number of stars and a short text. Each review is a row in a new reviews table that keeps the product and the user who wrote it.
The author comes from the login. loginRequired already finds the user from the token and passes it to your method. Save user.get("id") as the author, never a user_id from the body: the client writes the body, so it could claim to be anyone. The same idea on another route:
@PostMapping("/wishlist")
Object addToWishlist(HttpServletRequest request, @RequestBody Map<String, Object> data) {
return loginRequired(request, user -> saveWish(user, data));
}
Object saveWish(Map<String, Object> user, Map<String, Object> data) {
db.update("INSERT INTO wishlist (user_id, product_id) VALUES (?, ?)", user.get("id"), data.get("product_id"));
return ResponseEntity.status(201).body(Map.of("message", "Added"));
}
Before saving, check the request in this order: the product must exist (404 if not), the stars must be a whole number from 1 to 5 (400, naming the field), and the user must not have reviewed this product before (409 Conflict). A request without a login never reaches your method: loginRequired answers 401.
Ana signs up (she gets id 2) and logs in; the admin, already in the table, is user 1. Your route must answer:
POST /products/3/reviews no token {"stars": 5, "text": "Small but bright"}
→ 401 {"error": "Login required"}
POST /products/3/reviews Ana's token {"stars": 5, "text": "Small but bright", "user_id": 1}
→ 201 {"id": 1, "product_id": 3, "stars": 5, "text": "Small but bright", "user_id": 2}
POST /products/3/reviews Ana's token {"stars": 1, "text": "Changed my mind"}
→ 409 {"error": "You already reviewed this product"}
POST /products/1/reviews Ana's token {"stars": 6, "text": "Best mug ever"}
→ 400 {"error": "Stars must be a whole number from 1 to 5", "field": "stars"}
POST /products/1/reviews Ana's token {"stars": 0, "text": "Broke on day one"}
→ 400 {"error": "Stars must be a whole number from 1 to 5", "field": "stars"}
POST /products/99/reviews Ana's token {"stars": 4, "text": "Nice"}
→ 404 {"error": "Product not found"}
POST /products/3/reviews admin's token {"stars": 3, "text": "Too dim for reading"}
→ 201 {"id": 2, "product_id": 3, "stars": 3, "text": "Too dim for reading", "user_id": 1}
Your task: create the reviews table in the constructor. The route POST /products/{productId}/reviews is ready: it goes through loginRequired to saveReview(user, productId, data), which already checks the product. Finish saveReview: check the stars and an earlier review by the same user, save the review with the logged-in user as its author, and answer 201 with the new review.
Assignment
Follow the Coding Tutorial to create the reviews table and finish saveReview, one check at a time.
Hint
The stars check works like the price check in saveProduct: instanceof Integer stars, then the two ends. Do the checks in the order product, stars, earlier review, and only then insert the review with RETURNING id.
Free plan: 30 tutor messages. PRO is unlimited.
