You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2023/05/26 10:30:49 UTC

[james-project] 04/07: JAMES-3909 PushDeleteUserDataTaskStep

This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 08b5e1a8e800cdec8a443c2166651a2c5a04beb7
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 16:25:12 2023 +0700

    JAMES-3909 PushDeleteUserDataTaskStep
---
 .../CassandraPushSubscriptionDAO.java              | 10 ++++
 .../CassandraPushSubscriptionRepository.java       |  5 ++
 ...sitory.java => PushDeleteUserDataTaskStep.java} | 37 +++++++------
 .../PushSubscriptionRepository.java                |  2 +
 .../MemoryPushSubscriptionRepository.java          |  5 ++
 .../PushDeleteUserDataTaskStepTest.scala           | 63 ++++++++++++++++++++++
 .../PushSubscriptionRepositoryContract.scala       | 23 ++++++++
 7 files changed, 128 insertions(+), 17 deletions(-)

diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java
index d3eb92afd7..2738600992 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionDAO.java
@@ -73,6 +73,7 @@ public class CassandraPushSubscriptionDAO {
     private final PreparedStatement insert;
     private final PreparedStatement selectAll;
     private final PreparedStatement deleteOne;
+    private final PreparedStatement deleteAll;
 
     @Inject
     public CassandraPushSubscriptionDAO(CqlSession session, TypeStateFactory typeStateFactory) {
@@ -100,6 +101,10 @@ public class CassandraPushSubscriptionDAO {
             .whereColumn(DEVICE_CLIENT_ID).isEqualTo(bindMarker(DEVICE_CLIENT_ID))
             .build());
 
+        deleteAll = session.prepare(deleteFrom(TABLE_NAME)
+            .whereColumn(USER).isEqualTo(bindMarker(USER))
+            .build());
+
         this.typeStateFactory = typeStateFactory;
     }
 
@@ -138,6 +143,11 @@ public class CassandraPushSubscriptionDAO {
             .setString(DEVICE_CLIENT_ID, deviceClientId));
     }
 
+    public Mono<Void> deleteAll(Username username) {
+        return executor.executeVoid(deleteAll.bind()
+            .setString(USER, username.asString()));
+    }
+
     private PushSubscription toPushSubscription(Row row) {
         return PushSubscription.apply(
             PushSubscriptionId.apply(row.getUuid(ID)),
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
index ab901e1b62..47dd50f047 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
@@ -122,6 +122,11 @@ public class CassandraPushSubscriptionRepository implements PushSubscriptionRepo
             .switchIfEmpty(Mono.empty());
     }
 
+    @Override
+    public Publisher<Void> delete(Username username) {
+        return dao.deleteAll(username);
+    }
+
     @Override
     public Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids) {
         return dao.selectAll(username)
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStep.java
similarity index 56%
copy from server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
copy to server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStep.java
index f8fa49f08f..21d06def90 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStep.java
@@ -19,29 +19,32 @@
 
 package org.apache.james.jmap.api.pushsubscription;
 
-import java.time.ZonedDateTime;
-import java.util.Set;
+import javax.inject.Inject;
 
 import org.apache.james.core.Username;
-import org.apache.james.jmap.api.model.PushSubscription;
-import org.apache.james.jmap.api.model.PushSubscriptionCreationRequest;
-import org.apache.james.jmap.api.model.PushSubscriptionExpiredTime;
-import org.apache.james.jmap.api.model.PushSubscriptionId;
-import org.apache.james.jmap.api.model.TypeName;
+import org.apache.james.user.api.DeleteUserDataTaskStep;
 import org.reactivestreams.Publisher;
 
-public interface PushSubscriptionRepository {
-    Publisher<PushSubscription> save(Username username, PushSubscriptionCreationRequest pushSubscriptionCreationRequest);
+public class PushDeleteUserDataTaskStep implements DeleteUserDataTaskStep {
+    private final PushSubscriptionRepository pushSubscriptionRepository;
 
-    Publisher<PushSubscriptionExpiredTime> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire);
+    @Inject
+    public PushDeleteUserDataTaskStep(PushSubscriptionRepository pushSubscriptionRepository) {
+        this.pushSubscriptionRepository = pushSubscriptionRepository;
+    }
 
-    Publisher<Void> updateTypes(Username username, PushSubscriptionId id, Set<TypeName> types);
+    @Override
+    public StepName name() {
+        return new StepName("PushDeleteUserDataTaskStep");
+    }
 
-    Publisher<Void> validateVerificationCode(Username username, PushSubscriptionId id);
+    @Override
+    public int priority() {
+        return 3;
+    }
 
-    Publisher<Void> revoke(Username username, PushSubscriptionId id);
-
-    Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids);
-
-    Publisher<PushSubscription> list(Username username);
+    @Override
+    public Publisher<Void> deleteUserData(Username username) {
+        return pushSubscriptionRepository.delete(username);
+    }
 }
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
index f8fa49f08f..f1766cc9e9 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
@@ -41,6 +41,8 @@ public interface PushSubscriptionRepository {
 
     Publisher<Void> revoke(Username username, PushSubscriptionId id);
 
+    Publisher<Void> delete(Username username);
+
     Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids);
 
     Publisher<PushSubscription> list(Username username);
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
index aff9a18454..c7cf16ac85 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
@@ -117,6 +117,11 @@ public class MemoryPushSubscriptionRepository implements PushSubscriptionReposit
         return Mono.fromCallable(() -> table.remove(username, id)).then();
     }
 
+    @Override
+    public Publisher<Void> delete(Username username) {
+        return Mono.fromCallable(() -> table.rowMap().remove(username)).then();
+    }
+
     @Override
     public Publisher<PushSubscription> get(Username username, Set<PushSubscriptionId> ids) {
         return Flux.fromStream(table.row(username).entrySet().stream())
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
new file mode 100644
index 0000000000..e369becf43
--- /dev/null
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushDeleteUserDataTaskStepTest.scala
@@ -0,0 +1,63 @@
+/** ****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ * *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ * **************************************************************** */
+
+package org.apache.james.jmap.api.pushsubscription
+
+import java.net.URL
+import java.time.Clock
+
+import org.apache.james.jmap.api.identity.CustomIdentityDAOContract.bob
+import org.apache.james.jmap.api.model.{DeviceClientId, PushSubscriptionCreationRequest, PushSubscriptionServerURL}
+import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepositoryContract.ALICE
+import org.apache.james.jmap.memory.pushsubscription.MemoryPushSubscriptionRepository
+import org.assertj.core.api.Assertions.{assertThat, assertThatCode}
+import org.junit.jupiter.api.{BeforeEach, Test}
+import reactor.core.publisher.Flux
+import reactor.core.scala.publisher.SMono
+
+class PushDeleteUserDataTaskStepTest {
+  var pushSubscriptionRepository: PushSubscriptionRepository = _
+  var testee: PushDeleteUserDataTaskStep = _
+
+  @BeforeEach
+  def beforeEach(): Unit = {
+    pushSubscriptionRepository = new MemoryPushSubscriptionRepository(Clock.systemUTC())
+    testee = new PushDeleteUserDataTaskStep(pushSubscriptionRepository)
+  }
+
+  @Test
+  def shouldBeIdempotent(): Unit = {
+    assertThatCode(() => SMono(testee.deleteUserData(bob)).block())
+      .doesNotThrowAnyException()
+  }
+
+  @Test
+  def shouldDeleteUserData(): Unit = {
+    val validRequest = PushSubscriptionCreationRequest(
+      deviceClientId = DeviceClientId("1"),
+      url = PushSubscriptionServerURL(new URL("https://example.com/push")),
+      types = Seq(CustomTypeName1))
+    SMono.fromPublisher(pushSubscriptionRepository.save(ALICE, validRequest)).block().id
+
+    SMono(testee.deleteUserData(ALICE)).block()
+
+    assertThat(Flux.from(pushSubscriptionRepository.list(ALICE)).collectList().block())
+      .isEmpty()
+  }
+}
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
index 0ff84ad8e7..209563d0e9 100644
--- a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
@@ -247,6 +247,22 @@ trait PushSubscriptionRepositoryContract {
     assertThat(remaining).isEmpty()
   }
 
+  @Test
+  def deleteStoredSubscriptionShouldSucceed(): Unit = {
+    val validRequest = PushSubscriptionCreationRequest(
+      deviceClientId = DeviceClientId("1"),
+      url = PushSubscriptionServerURL(new URL("https://example.com/push")),
+      types = Seq(CustomTypeName1))
+    val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE, validRequest)).block().id
+    val singleRecordSaved = SFlux.fromPublisher(testee.get(ALICE, Set(pushSubscriptionId).asJava)).count().block()
+    assertThat(singleRecordSaved).isEqualTo(1)
+
+    SMono.fromPublisher(testee.delete(ALICE)).block()
+    val remaining = SFlux.fromPublisher(testee.get(ALICE, Set(pushSubscriptionId).asJava)).collectSeq().block().asJava
+
+    assertThat(remaining).isEmpty()
+  }
+
   @Test
   def revokeNotFoundShouldNotFail(): Unit = {
     val pushSubscriptionId = PushSubscriptionId.generate()
@@ -254,6 +270,13 @@ trait PushSubscriptionRepositoryContract {
       .doesNotThrowAnyException()
   }
 
+  @Test
+  def deleteNotFoundShouldNotFail(): Unit = {
+    val pushSubscriptionId = PushSubscriptionId.generate()
+    assertThatCode(() => SMono.fromPublisher(testee.delete(ALICE)).block())
+      .doesNotThrowAnyException()
+  }
+
   @Test
   def getStoredSubscriptionShouldSucceed(): Unit = {
     val deviceClientId1 = DeviceClientId("1")


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org