Skip to content

Commit 5e0efce

Browse files
Merge pull request #70 from trynoice/refactor/remove-redundant-soft-deletes
Remove soft deletes
2 parents 0b34d07 + 4f58518 commit 5e0efce

30 files changed

Lines changed: 220 additions & 651 deletions

src/integrationTest/java/com/trynoice/api/identity/AccountControllerTest.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.trynoice.api.identity.entities.AuthUser;
5+
import com.trynoice.api.identity.entities.RefreshToken;
56
import com.trynoice.api.identity.exceptions.SignInTokenDispatchException;
67
import com.trynoice.api.identity.payload.AuthCredentialsResponse;
78
import com.trynoice.api.identity.payload.SignInParams;
@@ -357,11 +358,18 @@ void deleteAccount() throws Exception {
357358
.andExpect(status().is(HttpStatus.NO_CONTENT.value()));
358359

359360
// validate that all existing refresh tokens have been revoked.
360-
assertTrue(refreshTokens.stream().noneMatch(t -> t.getDeletedAt() != null));
361+
refreshTokens.stream()
362+
.map(t -> entityManager.find(RefreshToken.class, t.getId()))
363+
.forEach(t -> assertTrue(t.getExpiresAt().isBefore(OffsetDateTime.now())));
361364

362-
// perform the request again to ensure access token no longer works
365+
// validate that account has been deactivated.'
366+
Stream.of(user)
367+
.map(u -> entityManager.find(AuthUser.class, u.getId()))
368+
.forEach(u -> assertNotNull(u.getDeactivatedAt()));
369+
370+
// perform a request to ensure access token no longer works
363371
mockMvc.perform(
364-
delete(urlFmt, anotherUser.getId())
372+
get("/v1/accounts/profile")
365373
.header("Authorization", "Bearer " + accessToken))
366374
.andExpect(status().is(HttpStatus.UNAUTHORIZED.value()));
367375
}

src/integrationTest/java/com/trynoice/api/identity/entities/RefreshTokenRepositoryTest.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.transaction.annotation.Transactional;
88

99
import javax.persistence.EntityManager;
10+
import java.time.OffsetDateTime;
1011
import java.util.stream.Collectors;
1112
import java.util.stream.IntStream;
1213

@@ -25,7 +26,7 @@ public class RefreshTokenRepositoryTest {
2526
private RefreshTokenRepository refreshTokenRepository;
2627

2728
@Test
28-
void deleteAllByOwnerId() {
29+
void updateExpiresAtOfAllByOwnerId() {
2930
val user = createAuthUser(entityManager);
3031

3132
val ownedRefreshTokens = IntStream.range(0, 5)
@@ -36,13 +37,13 @@ void deleteAllByOwnerId() {
3637
.mapToObj(i -> createRefreshToken(entityManager, createAuthUser(entityManager)))
3738
.collect(Collectors.toUnmodifiableList());
3839

39-
refreshTokenRepository.deleteAllByOwnerId(user.getId());
40-
assertTrue(
41-
ownedRefreshTokens.stream()
42-
.noneMatch(t -> refreshTokenRepository.existsById(t.getId())));
40+
refreshTokenRepository.updateExpiresAtOfAllByOwnerId(OffsetDateTime.now(), user.getId());
41+
ownedRefreshTokens.stream()
42+
.map(t -> entityManager.find(RefreshToken.class, t.getId()))
43+
.forEach(t -> assertTrue(t.getExpiresAt().isBefore(OffsetDateTime.now())));
4344

44-
assertTrue(
45-
unownedRefreshTokens.stream()
46-
.allMatch(t -> refreshTokenRepository.existsById(t.getId())));
45+
unownedRefreshTokens.stream()
46+
.map(t -> entityManager.find(RefreshToken.class, t.getId()))
47+
.forEach(t -> assertTrue(t.getExpiresAt().isAfter(OffsetDateTime.now())));
4748
}
4849
}

src/integrationTest/java/com/trynoice/api/platform/BasicEntityRepositoryTest.java

Lines changed: 0 additions & 150 deletions
This file was deleted.

src/integrationTest/java/com/trynoice/api/platform/TestConfig.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/integrationTest/java/com/trynoice/api/platform/TestEntity.java

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/integrationTest/java/com/trynoice/api/platform/TestEntityRepository.java

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/integrationTest/java/com/trynoice/api/subscription/SubscriptionTestUtils.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,13 @@ public static Subscription buildSubscription(
6666

6767
@NonNull
6868
static Customer buildCustomer(@NonNull EntityManager entityManager, @NonNull AuthUser user) {
69-
val customer = Customer.builder()
70-
.userId(user.getId())
71-
.stripeId(UUID.randomUUID().toString())
72-
.build();
69+
val customer = Optional.ofNullable(entityManager.find(Customer.class, user.getId()))
70+
.orElse(
71+
Customer.builder()
72+
.userId(user.getId())
73+
.build());
7374

75+
customer.setStripeId(UUID.randomUUID().toString());
7476
return entityManager.merge(customer);
7577
}
7678

src/integrationTest/java/com/trynoice/api/testing/AuthTestUtils.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,18 @@ public static String createSignedRefreshJwt(
9292

9393
val jwtSigningAlgorithm = Algorithm.HMAC256(hmacSecret);
9494
if (type != JwtType.REUSED) {
95-
return refreshToken.getJwt(jwtSigningAlgorithm);
95+
return refreshToken.toSignedJwt(jwtSigningAlgorithm);
9696
}
9797

9898
// create a separate entity that is not attached to the entity manager.
99-
val usedRefreshToken = RefreshToken.builder()
99+
return RefreshToken.builder()
100+
.id(refreshToken.getId())
101+
.createdAt(refreshToken.getCreatedAt())
100102
.owner(owner)
101103
.expiresAt(refreshToken.getExpiresAt())
102104
.ordinal(refreshToken.getOrdinal() + 1)
103-
.build();
104-
105-
usedRefreshToken.setId(refreshToken.getId());
106-
usedRefreshToken.setCreatedAt(refreshToken.getCreatedAt());
107-
108-
return usedRefreshToken.getJwt(jwtSigningAlgorithm);
105+
.build()
106+
.toSignedJwt(jwtSigningAlgorithm);
109107
}
110108

111109
/**

src/main/java/com/trynoice/api/identity/AccountController.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,18 @@ ResponseEntity<Void> updateProfile(
271271
}
272272

273273
/**
274-
* Deletes the account of an authenticated user. If the account with the given {@literal
275-
* accountId} does not belong to the authenticated user, it returns {@literal HTTP 400}.
274+
* <p>
275+
* Schedules an account for deletion. If the account with the given {@literal accountId} does
276+
* not belong to the authenticated user, it returns {@literal HTTP 400}.</p>
277+
*
278+
* <p>
279+
* Users will immediately lose access to their accounts and their account will be deactivated.
280+
* Deactivated accounts are permanently deleted after some time. Users can restore their
281+
* deactivated accounts before permanent deletion by completing a successful sign-in.</p>
276282
*
277283
* @param accountId must be the account id of the authenticated user.
278284
*/
279-
@Operation(summary = "Delete account of the auth user")
285+
@Operation(summary = "Schedule the account of the auth user for deletion")
280286
@ApiResponses({
281287
@ApiResponse(responseCode = "204", description = "account deleted successfully"),
282288
@ApiResponse(responseCode = "400", description = "request is not valid"),

0 commit comments

Comments
 (0)