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

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

commit 249f67596d6d04fcf77a69daa975694968f55977
Author: Benoit TELLIER <btell...@linagora.com>
AuthorDate: Tue Jun 3 22:34:46 2025 +0200

    JAMES-4316 Routes for getting and deleted mapping sources
---
 .../modules/servers/partials/operate/webadmin.adoc |  44 ++++++-
 .../james/webadmin/routes/MappingRoutes.java       |  39 ++++++
 .../james/webadmin/routes/MappingRoutesTest.java   | 133 +++++++++++++++++++++
 src/site/markdown/server/manage-webadmin.md        |  42 +++++++
 4 files changed, 257 insertions(+), 1 deletion(-)

diff --git a/docs/modules/servers/partials/operate/webadmin.adoc 
b/docs/modules/servers/partials/operate/webadmin.adoc
index 8fe440c42c..bea121ca14 100644
--- a/docs/modules/servers/partials/operate/webadmin.adoc
+++ b/docs/modules/servers/partials/operate/webadmin.adoc
@@ -3482,7 +3482,7 @@ Response code:
 
 === Listing User Mappings
 
-This endpoint allows receiving all mappings of a corresponding user.
+This endpoint allows receiving all mappings originating from a corresponding 
user.
 
 ....
 curl -XGET http://ip:port/mappings/user/{userAddress}
@@ -3516,6 +3516,48 @@ Response codes:
 * 200: OK
 * 400: Invalid parameter value
 
+=== Listing sources for a mapping
+
+This endpoint allows receiving all mappings pointing to a corresponding user.
+
+....
+curl -XGET http://ip:port/mappings/sources/{userAddress}?type={type}
+....
+
+Return all mappings of a user where:
+
+* `userAddress`: is the selected user
+* `type`: Type of the mapping. One of `group`, `forward`, `address`, `alias`. 
Compulsory.
+
+Response body:
+
+....
+["gro...@domain.tld","gro...@domain.tld"]
+....
+
+Response codes:
+
+* 200: OK
+* 400: Invalid parameter value
+
+=== Deleting sources for a mapping
+
+This endpoint allows deleting all mappings pointing to a corresponding user.
+
+....
+curl -XDELETE http://ip:port/mappings/sources/{userAddress}?type={type}
+....
+
+Deletes all mappings of a user where:
+
+* `userAddress`: is the selected user
+* `type`: Type of the mapping. One of `group`, `forward`, `address`, `alias`. 
Compulsory.
+
+Response codes:
+
+* 204: OK
+* 400: Invalid parameter value
+
 == Administrating mail repositories
 
 === Create a mail repository
diff --git 
a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MappingRoutes.java
 
b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MappingRoutes.java
index 8e33fe9018..1d7244bd8d 100644
--- 
a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MappingRoutes.java
+++ 
b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/MappingRoutes.java
@@ -19,7 +19,12 @@
 
 package org.apache.james.webadmin.routes;
 
+import static spark.Spark.halt;
+
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import jakarta.inject.Inject;
 
@@ -39,7 +44,9 @@ import org.apache.james.webadmin.utils.Responses;
 import org.eclipse.jetty.http.HttpStatus;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.github.fge.lambdas.Throwing;
 
+import spark.HaltException;
 import spark.Request;
 import spark.Response;
 import spark.Service;
@@ -48,7 +55,9 @@ public class MappingRoutes implements Routes {
 
     static final String BASE_PATH = "/mappings";
     static final String USER_MAPPING_PATH = "/mappings/user/";
+    static final String SOURCE_MAPPING_PATH = "/mappings/sources/";
     static final String USER = "user";
+    static final String SOURCE = "source";
 
     private final JsonTransformer jsonTransformer;
     private final JsonExtractor<Map<MappingSource, Mappings>> jsonExtractor;
@@ -72,6 +81,8 @@ public class MappingRoutes implements Routes {
         service.get(BASE_PATH, this::getMappings, jsonTransformer);
         service.put(BASE_PATH, this::addMappings);
         service.get(USER_MAPPING_PATH + ":" + USER, this::getUserMappings, 
jsonTransformer);
+        service.get(SOURCE_MAPPING_PATH + ":" + SOURCE, 
this::getMappingSources, jsonTransformer);
+        service.delete(SOURCE_MAPPING_PATH + ":" + SOURCE, 
this::deleteMappingSources, jsonTransformer);
     }
 
     private Map<MappingSource, Mappings> getMappings(Request request, Response 
response) {
@@ -116,4 +127,32 @@ public class MappingRoutes implements Routes {
         Username username = Username.of(request.params(USER).toLowerCase());
         return 
recipientRewriteTable.getStoredMappings(MappingSource.fromUser(username));
     }
+
+    private List<String> getMappingSources(Request request, Response response) 
{
+        Mapping mapping = Mapping.of(extractType(request), 
request.params(SOURCE));
+
+        return recipientRewriteTable.listSourcesReactive(mapping)
+            .map(MappingSource::asMailAddressString)
+            .collectList()
+            .block();
+    }
+
+    private HaltException deleteMappingSources(Request request, Response 
response) {
+        Mapping mapping = Mapping.of(extractType(request), 
request.params(SOURCE));
+
+        recipientRewriteTable.listSourcesReactive(mapping)
+            .collectList()
+            .block()
+            .forEach(Throwing.consumer(source -> 
recipientRewriteTable.removeMapping(source, mapping)));
+
+        return halt(HttpStatus.NO_CONTENT_204);
+    }
+
+    private static Mapping.Type extractType(Request request) {
+        return Optional.ofNullable(request.queryParams("type")).map(name -> 
Arrays.stream(Mapping.Type.values())
+                .filter(v -> v.name().equalsIgnoreCase(name))
+                .findAny()
+                .orElseThrow(() -> new IllegalArgumentException("Invalid 
'type' query parameter: " + name)))
+            .orElseThrow(() -> new IllegalArgumentException("On reversed 
resolution 'type' is compulsory"));
+    }
 }
diff --git 
a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
index 9e01a1b773..0f8ec6ace7 100644
--- 
a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
+++ 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
@@ -282,6 +282,139 @@ class MappingRoutesTest {
                 "}");
     }
 
+    @Test
+    void getSourcesShouldReturnGroupMappings() throws Exception {
+        MailAddress groupAddress = new MailAddress("gr...@domain.tld");
+        MailAddress group2Address = new MailAddress("gro...@domain.tld");
+        MailAddress group3Address = new MailAddress("gro...@domain.tld");
+
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(groupAddress), "memb...@domain.tld");
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(group2Address), 
"memb...@domain.tld");
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(groupAddress), "memb...@domain.tld");
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(group3Address), 
"memb...@domain.tld");
+
+        String jsonBody = given()
+            .queryParam("type", "group")
+        .when()
+            .get("/sources/memb...@domain.tld")
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.OK_200)
+            .extract()
+            .body()
+            .asString();
+
+        assertThatJson(jsonBody)
+            .when(Option.IGNORING_ARRAY_ORDER)
+            .isEqualTo("""
+                ["gr...@domain.tld","gro...@domain.tld"]""");
+    }
+
+    @Test
+    void deleteShouldBeIdempotent() {
+        given()
+            .queryParam("type", "group")
+        .when()
+            .delete("/sources/memb...@domain.tld")
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.NO_CONTENT_204);
+    }
+
+    @Test
+    void shouldNotReturnDeletedSources() throws Exception {
+        MailAddress groupAddress = new MailAddress("gr...@domain.tld");
+        MailAddress group2Address = new MailAddress("gro...@domain.tld");
+        MailAddress group3Address = new MailAddress("gro...@domain.tld");
+
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(groupAddress), "memb...@domain.tld");
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(group2Address), 
"memb...@domain.tld");
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(groupAddress), "memb...@domain.tld");
+        recipientRewriteTable.addGroupMapping(
+            MappingSource.fromMailAddress(group3Address), 
"memb...@domain.tld");
+
+        given()
+            .queryParam("type", "group")
+        .when()
+            .delete("/sources/memb...@domain.tld")
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.NO_CONTENT_204);
+
+        String jsonBody = when()
+            .get()
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.OK_200)
+            .extract()
+            .body()
+            .asString();
+
+        assertThatJson(jsonBody)
+            .when(Option.IGNORING_ARRAY_ORDER)
+            .isEqualTo("{" +
+                "  \"gr...@domain.tld\": [" +
+                "    {" +
+                "      \"type\": \"Group\"," +
+                "      \"mapping\": \"memb...@domain.tld\"" +
+                "    }" +
+                "  ]," +
+                "  \"gro...@domain.tld\": [" +
+                "    {" +
+                "      \"type\": \"Group\"," +
+                "      \"mapping\": \"memb...@domain.tld\"" +
+                "    }" +
+                "  ]" +
+                "}");
+    }
+
+    @Test
+    void getSourcesShouldRejectInvalidType() {
+        given()
+            .queryParam("type", "invalid")
+        .when()
+            .get("/sources/memb...@domain.tld")
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.BAD_REQUEST_400);
+    }
+
+    @Test
+    void getSourcesShouldRejectNoType() {
+        when()
+            .get("/sources/memb...@domain.tld")
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.BAD_REQUEST_400);
+    }
+
+    @Test
+    void deleteSourcesShouldRejectInvalidType() {
+        given()
+            .queryParam("type", "invalid")
+        .when()
+            .delete("/sources/memb...@domain.tld")
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.BAD_REQUEST_400);
+    }
+
+    @Test
+    void deleteSourcesShouldRejectNoType() {
+        when()
+            .delete("/sources/memb...@domain.tld")
+        .then()
+            .contentType(ContentType.JSON)
+            .statusCode(HttpStatus.BAD_REQUEST_400);
+    }
+
     @Test
     void getMappingsShouldReturnForwardMappings() throws 
RecipientRewriteTableException {
         Username forwardUsername = Username.of("forwardu...@domain.tld");
diff --git a/src/site/markdown/server/manage-webadmin.md 
b/src/site/markdown/server/manage-webadmin.md
index 5a82d26dc6..47acb46356 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -3397,6 +3397,48 @@ Response codes:
 - 200: OK
 - 400: Invalid parameter value
 
+### Listing sources for a mapping
+
+This endpoint allows receiving all mappings pointing to a corresponding user.
+
+```
+curl -XGET http://ip:port/mappings/sources/{userAddress}?type={type}
+```
+
+Return all mappings of a user where:
+
+ - `userAddress`: is the selected user
+ - `type`: Type of the mapping. One of `group`, `forward`, `address`, `alias`. 
Compulsory.
+
+Response body:
+
+```
+["gro...@domain.tld","gro...@domain.tld"]
+```
+
+Response codes:
+
+ - 200: OK
+ - 400: Invalid parameter value
+
+### Deleting sources for a mapping
+
+This endpoint allows deleting all mappings pointing to a corresponding user.
+
+```
+curl -XDELETE http://ip:port/mappings/sources/{userAddress}?type={type}
+```
+
+Deletes all mappings of a user where:
+
+ - `userAddress`: is the selected user
+ - `type`: Type of the mapping. One of `group`, `forward`, `address`, `alias`. 
Compulsory.
+
+Response codes:
+
+ - 204: OK
+ - 400: Invalid parameter value
+
 ## Administrating mail repositories
 
  - [Create a mail repository](#Create_a_mail_repository)


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

Reply via email to