Share a wishlist by link

Java · Spring Boot · beginner · modification

Customers can now share a wishlist by link, for example with family before a birthday. `POST /api/wishlists/{id}/share` gives the wishlist a random share token (or returns the one it already has), `DELETE` on the same path clears it, and `GET /api/public/wishlists/{token}` shows the list to anyone holding the link. The token is stored in the new `share_token` column on `Wishlist`. `WishlistShareService` holds the logic; the owner endpoints load the wishlist with a row lock, scoped to the signed-in customer.

Shop backend on Spring Boot 3.3.5 (Java 21), Spring Data JPA with Hibernate 6.5 on PostgreSQL 16 at its default isolation (read committed); Hibernate takes a row write lock for `@Lock(PESSIMISTIC_WRITE)` (`SELECT ... FOR NO KEY UPDATE` on PostgreSQL), which a second locking read of the same row waits on. The existing, unchanged security config requires a signed-in session on every `/api/**` route except `/api/public/**` and the `/error` page, which it lets anonymous visitors call (so a 404 from the public route keeps its status through the error dispatch), and it handles CSRF for the session routes. `CurrentCustomer.id()` returns the signed-in customer's id and is never null on a signed-in route. Existing and unchanged: the rest of `Wishlist`; `WishlistItem` (wishlist id, product id, product name, `addedAt`); `WishlistItemRepository.findByWishlistIdOrderByAddedAtAsc`; the `WishlistItemView` record (`productId`, `productName`) with `WishlistItemView.from(item)`; `WishlistNotFoundException`, annotated `@ResponseStatus(NOT_FOUND)`; and `WishlistController` (`GET /api/wishlists` and `GET /api/wishlists/{id}`), whose owner endpoints return `WishlistView` records, never the entity. Errors use Spring Boot's default error body with `server.error.include-message` at its default (`never`). A migration that ships separately and is already applied added the nullable `share_token varchar(43)` column to `wishlists` with a unique constraint. Deleting a wishlist (existing, unchanged) deletes its row and its items.

Requirements

Files touched

--- src/main/java/com/acme/shop/wishlist/Wishlist.java
     @Column(name = "created_at", nullable = false)
     private Instant createdAt;
+
+    @Column(name = "share_token", unique = true, length = 43)
+    private String shareToken;
 
     protected Wishlist() {
         return createdAt;
     }
+
+    public String getShareToken() {
+        return shareToken;
+    }
+
+    public void setShareToken(String shareToken) {
+        this.shareToken = shareToken;
+    }
 }
 
--- src/main/java/com/acme/shop/wishlist/WishlistRepository.java
 package com.acme.shop.wishlist;
 
+import jakarta.persistence.LockModeType;
 import java.util.List;
 import java.util.Optional;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
 
 public interface WishlistRepository extends JpaRepository<Wishlist, Long> {

Review this PR

Java practice