From 44fd39870109575dd9fc8c48a4c1bb6c011b4ccf Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Fri, 10 Apr 2026 22:04:24 +0200 Subject: [PATCH] fix(invite): saveAndFlush invalidation before INSERT + set invalidated_at on accept - createInvite: use saveAndFlush when invalidating existing invite so the UPDATE is guaranteed to hit the DB before the new INSERT, preventing duplicate key violation on uq_household_invite_active - acceptInvite: also set invalidated_at when marking invite as used, so accepted invites are fully removed from the partial unique index and cannot block future invite creation Co-Authored-By: Claude Sonnet 4.6 --- backend/Dockerfile | 1 + .../main/java/com/recipeapp/household/HouseholdService.java | 3 ++- .../java/com/recipeapp/household/HouseholdServiceTest.java | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 0b4c221..e62a95d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,6 +7,7 @@ COPY src src RUN ./mvnw package -DskipTests -B FROM eclipse-temurin:21-jre-alpine +RUN apk add --no-cache libwebp WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 diff --git a/backend/src/main/java/com/recipeapp/household/HouseholdService.java b/backend/src/main/java/com/recipeapp/household/HouseholdService.java index cb26329..1fa75dd 100644 --- a/backend/src/main/java/com/recipeapp/household/HouseholdService.java +++ b/backend/src/main/java/com/recipeapp/household/HouseholdService.java @@ -165,7 +165,7 @@ public class HouseholdService { householdInviteRepository.findByHouseholdIdAndInvalidatedAtIsNull(household.getId()) .ifPresent(existing -> { existing.setInvalidatedAt(Instant.now()); - householdInviteRepository.save(existing); + householdInviteRepository.saveAndFlush(existing); }); String code = generateInviteCode(); @@ -211,6 +211,7 @@ public class HouseholdService { new UserAccount(email, name, passwordEncoder.encode(rawPassword))); invite.setStatus("used"); + invite.setInvalidatedAt(Instant.now()); householdInviteRepository.save(invite); Household household = invite.getHousehold(); diff --git a/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java b/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java index 650f517..5e36228 100644 --- a/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java +++ b/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java @@ -507,11 +507,13 @@ class HouseholdServiceTest { when(householdMemberRepository.findByUserEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(member)); when(householdInviteRepository.findByHouseholdIdAndInvalidatedAtIsNull(any())).thenReturn(Optional.of(existingInvite)); + when(householdInviteRepository.saveAndFlush(any(HouseholdInvite.class))).thenAnswer(i -> i.getArgument(0)); when(householdInviteRepository.save(any(HouseholdInvite.class))).thenAnswer(i -> i.getArgument(0)); householdService.createInvite("sarah@example.com"); assertThat(existingInvite.getInvalidatedAt()).isNotNull(); - verify(householdInviteRepository, times(2)).save(any(HouseholdInvite.class)); + verify(householdInviteRepository).saveAndFlush(existingInvite); + verify(householdInviteRepository).save(any(HouseholdInvite.class)); } }