You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by as...@apache.org on 2022/04/13 21:26:46 UTC

[unomi] 01/01: UNOMI-487 Add profile alias management to REST API and GraphQL API

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

asi pushed a commit to branch Unomi-487-backup
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit ce36a4c27f51e6a7887cff548f008e304c46cb43
Author: Anatol Sialitski <as...@enonic.com>
AuthorDate: Thu Feb 17 18:16:35 2022 +0300

    UNOMI-487 Add profile alias management to REST API and GraphQL API
---
 .../apache/unomi/api/services/ProfileService.java  |   4 +
 .../graphql/commands/AddAliasToProfileCommand.java |  66 +++++++++++++
 .../commands/RemoveAliasFromProfileCommand.java    |  69 ++++++++++++++
 .../factories/ProfileAliasConditionFactory.java    |  77 +++++++++++++++
 .../FindProfileAliasConnectionDataFetcher.java     |  71 ++++++++++++++
 .../profile/GetProfileAliasesDataFetcher.java      |  47 ++++++++++
 .../fetchers/profile/ProfileAliasDataFetcher.java  |  41 ++++++++
 .../types/input/CDPProfileAliasFilterInput.java    |  83 ++++++++++++++++
 .../graphql/types/input/CDPProfileAliasInput.java  |  53 +++++++++++
 .../unomi/graphql/types/output/CDPMutation.java    |  27 ++++++
 .../graphql/types/output/CDPProfileAlias.java      |  66 +++++++++++++
 .../types/output/CDPProfileAliasConnection.java    |  66 +++++++++++++
 .../graphql/types/output/CDPProfileAliasEdge.java  |  49 ++++++++++
 .../unomi/graphql/types/output/CDPQuery.java       |  36 ++++++-
 .../org/apache/unomi/itests/ProfileServiceIT.java  |  17 +++-
 .../itests/graphql/GraphQLProfileAliasesIT.java    | 104 +++++++++++++++++++++
 .../graphql/profileAlias/addAliasToProfile.json    |  16 ++++
 .../graphql/profileAlias/findProfileAliases.json   |   8 ++
 .../graphql/profileAlias/getProfileAlias.json      |   5 +
 .../graphql/profileAlias/getProfileAliases.json    |   5 +
 .../profileAlias/removeAliasFromProfile.json       |  13 +++
 .../rest/endpoints/ProfileServiceEndPoint.java     |  27 ++++++
 .../services/impl/profiles/ProfileServiceImpl.java |  45 +++++++++
 23 files changed, 989 insertions(+), 6 deletions(-)

diff --git a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
index b2698c424..2f03e5246 100644
--- a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
@@ -110,6 +110,10 @@ public interface ProfileService {
 
     void addAliasToProfile(String profileID, String alias, String clientID);
 
+    ProfileAlias removeAliasFromProfile(String profileID, String alias, String clientID);
+
+    PartialList<ProfileAlias> findProfileAliases(String profileId, int offset, int size, String sortBy);
+
     /**
      * Merge the specified profile properties in an existing profile,or save new profile if it does not exist yet
      *
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/AddAliasToProfileCommand.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/AddAliasToProfileCommand.java
new file mode 100644
index 000000000..4ba18d570
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/AddAliasToProfileCommand.java
@@ -0,0 +1,66 @@
+/*
+ * 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.unomi.graphql.commands;
+
+import org.apache.unomi.api.ProfileAlias;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.graphql.types.input.CDPProfileAliasInput;
+import org.apache.unomi.graphql.types.output.CDPProfileAlias;
+import org.apache.unomi.persistence.spi.PersistenceService;
+
+public class AddAliasToProfileCommand extends BaseCommand<CDPProfileAlias> {
+
+    private final CDPProfileAliasInput profileAliasInput;
+
+    public AddAliasToProfileCommand(Builder builder) {
+        super(builder);
+        this.profileAliasInput = builder.profileAliasInput;
+    }
+
+    @Override
+    public CDPProfileAlias execute() {
+        ProfileService profileService = serviceManager.getService(ProfileService.class);
+
+        profileService.addAliasToProfile(
+                profileAliasInput.getProfileID().getId(),
+                profileAliasInput.getAlias(),
+                profileAliasInput.getProfileID().getClient().getId());
+
+        PersistenceService persistenceService = serviceManager.getService(PersistenceService.class);
+        persistenceService.refreshIndex(ProfileAlias.class, null);
+
+        return new CDPProfileAlias(persistenceService.load(profileAliasInput.getAlias(), ProfileAlias.class));
+    }
+
+    public static Builder create() {
+        return new Builder();
+    }
+
+    public static class Builder extends BaseCommand.Builder<Builder> {
+
+        private CDPProfileAliasInput profileAliasInput;
+
+        public Builder profileAliasInput(CDPProfileAliasInput profileAliasInput) {
+            this.profileAliasInput = profileAliasInput;
+            return this;
+        }
+
+        public AddAliasToProfileCommand build() {
+            return new AddAliasToProfileCommand(this);
+        }
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/RemoveAliasFromProfileCommand.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/RemoveAliasFromProfileCommand.java
new file mode 100644
index 000000000..63aa06529
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/RemoveAliasFromProfileCommand.java
@@ -0,0 +1,69 @@
+/*
+ * 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.unomi.graphql.commands;
+
+import org.apache.unomi.api.ProfileAlias;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.graphql.types.input.CDPProfileIDInput;
+import org.apache.unomi.graphql.types.output.CDPProfileAlias;
+
+public class RemoveAliasFromProfileCommand extends BaseCommand<CDPProfileAlias> {
+
+    private final String alias;
+
+    private final CDPProfileIDInput profileIDInput;
+
+    private RemoveAliasFromProfileCommand(Builder builder) {
+        super(builder);
+        this.alias = builder.alias;
+        this.profileIDInput = builder.profileIDInput;
+    }
+
+    @Override
+    public CDPProfileAlias execute() {
+        ProfileService profileService = serviceManager.getService(ProfileService.class);
+
+        ProfileAlias profileAlias = profileService.removeAliasFromProfile(profileIDInput.getId(), alias, profileIDInput.getClient().getId());
+
+        return profileAlias != null ? new CDPProfileAlias(profileAlias) : null;
+    }
+
+    public static Builder create() {
+        return new Builder();
+    }
+
+    public static class Builder extends BaseCommand.Builder<Builder> {
+
+        private String alias;
+
+        private CDPProfileIDInput profileIDInput;
+
+        public Builder setAlias(String alias) {
+            this.alias = alias;
+            return this;
+        }
+
+        public Builder setProfileIDInput(CDPProfileIDInput profileIDInput) {
+            this.profileIDInput = profileIDInput;
+            return this;
+        }
+
+        public RemoveAliasFromProfileCommand build() {
+            return new RemoveAliasFromProfileCommand(this);
+        }
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/condition/factories/ProfileAliasConditionFactory.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/condition/factories/ProfileAliasConditionFactory.java
new file mode 100644
index 000000000..28c6340aa
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/condition/factories/ProfileAliasConditionFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.unomi.graphql.condition.factories;
+
+import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.graphql.types.input.CDPProfileAliasFilterInput;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class ProfileAliasConditionFactory extends ConditionFactory {
+
+    private static ProfileAliasConditionFactory instance;
+
+    public static synchronized ProfileAliasConditionFactory get(final DataFetchingEnvironment environment) {
+        if (instance == null) {
+            instance = new ProfileAliasConditionFactory(environment);
+        }
+
+        return instance;
+    }
+
+    private ProfileAliasConditionFactory(final DataFetchingEnvironment environment) {
+        super("profileAliasesPropertyCondition", environment);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Condition filterInputCondition(final CDPProfileAliasFilterInput filterInput, final Map<String, Object> filterInputAsMap) {
+        if (filterInput == null) {
+            return matchAllCondition();
+        }
+
+        final List<Condition> rootSubConditions = new ArrayList<>();
+
+        if (filterInput.getAlias_equals() != null) {
+            rootSubConditions.add(propertyCondition("itemId", filterInput.getAlias_equals()));
+        }
+
+        if (filterInput.getProfileID_equals() != null) {
+            rootSubConditions.add(propertyCondition("profileID.keyword", filterInput.getProfileID_equals()));
+        }
+
+        if (filterInput.getClientID_equals() != null) {
+            rootSubConditions.add(propertyCondition("clientID.keyword", filterInput.getClientID_equals()));
+        }
+
+        if (filterInputAsMap.get("and") != null) {
+            final List<Map<String, Object>> andFilterInputAsMap = (List<Map<String, Object>>) filterInputAsMap.get("and");
+
+            rootSubConditions.add(filtersToCondition(filterInput.getAnd(), andFilterInputAsMap, this::filterInputCondition, "and"));
+        }
+
+        if (filterInputAsMap.get("or") != null) {
+            final List<Map<String, Object>> orFilterInputAsMap = (List<Map<String, Object>>) filterInputAsMap.get("or");
+
+            rootSubConditions.add(filtersToCondition(filterInput.getOr(), orFilterInputAsMap, this::filterInputCondition, "or"));
+        }
+
+        return booleanCondition("and", rootSubConditions);
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/FindProfileAliasConnectionDataFetcher.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/FindProfileAliasConnectionDataFetcher.java
new file mode 100644
index 000000000..e6dea6006
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/FindProfileAliasConnectionDataFetcher.java
@@ -0,0 +1,71 @@
+/*
+ * 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.unomi.graphql.fetchers.profile;
+
+import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.ProfileAlias;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.graphql.condition.factories.ProfileAliasConditionFactory;
+import org.apache.unomi.graphql.fetchers.BaseConnectionDataFetcher;
+import org.apache.unomi.graphql.fetchers.ConnectionParams;
+import org.apache.unomi.graphql.services.ServiceManager;
+import org.apache.unomi.graphql.types.input.CDPOrderByInput;
+import org.apache.unomi.graphql.types.input.CDPProfileAliasFilterInput;
+import org.apache.unomi.graphql.types.output.CDPPageInfo;
+import org.apache.unomi.graphql.types.output.CDPProfileAliasConnection;
+import org.apache.unomi.graphql.types.output.CDPProfileAliasEdge;
+import org.apache.unomi.persistence.spi.PersistenceService;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class FindProfileAliasConnectionDataFetcher extends BaseConnectionDataFetcher<CDPProfileAliasConnection> {
+
+    private final CDPProfileAliasFilterInput filterInput;
+
+    private final List<CDPOrderByInput> orderByInput;
+
+    public FindProfileAliasConnectionDataFetcher(
+            final CDPProfileAliasFilterInput filterInput, final List<CDPOrderByInput> orderByInput) {
+        this.filterInput = filterInput;
+        this.orderByInput = orderByInput;
+    }
+
+    @Override
+    public CDPProfileAliasConnection get(DataFetchingEnvironment environment) throws Exception {
+        final ServiceManager serviceManager = environment.getContext();
+
+        final PersistenceService persistenceService = serviceManager.getService(PersistenceService.class);
+
+        final ConnectionParams params = parseConnectionParams(environment);
+
+        final Query query = buildQuery(createCondition(environment), orderByInput, params);
+
+        final PartialList<ProfileAlias> partialList = persistenceService.query(
+                query.getCondition(), query.getSortby(), ProfileAlias.class, query.getOffset(), query.getLimit());
+
+        final List<CDPProfileAliasEdge> edges = partialList.getList().stream().map(CDPProfileAliasEdge::new).collect(Collectors.toList());
+
+        return new CDPProfileAliasConnection(edges, new CDPPageInfo(), partialList.getTotalSize());
+    }
+
+    private Condition createCondition(final DataFetchingEnvironment environment) {
+        return ProfileAliasConditionFactory.get(environment).filterInputCondition(filterInput, environment.getArgument("filter"));
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/GetProfileAliasesDataFetcher.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/GetProfileAliasesDataFetcher.java
new file mode 100644
index 000000000..0b14d3d33
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/GetProfileAliasesDataFetcher.java
@@ -0,0 +1,47 @@
+/*
+ * 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.unomi.graphql.fetchers.profile;
+
+import graphql.schema.DataFetcher;
+import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.ProfileAlias;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.graphql.services.ServiceManager;
+import org.apache.unomi.graphql.types.output.CDPProfileAlias;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class GetProfileAliasesDataFetcher implements DataFetcher<List<CDPProfileAlias>> {
+
+    private final String profileId;
+
+    public GetProfileAliasesDataFetcher(final String profileId) {
+        this.profileId = profileId;
+    }
+
+    @Override
+    public List<CDPProfileAlias> get(final DataFetchingEnvironment environment) throws Exception {
+        ServiceManager serviceManager = environment.getContext();
+
+        ProfileService profileService = serviceManager.getService(ProfileService.class);
+        PartialList<ProfileAlias> partialList = profileService.findProfileAliases(profileId, 0, 100, null);
+
+        return partialList.getList().stream().map(CDPProfileAlias::new).collect(Collectors.toList());
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/ProfileAliasDataFetcher.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/ProfileAliasDataFetcher.java
new file mode 100644
index 000000000..fd99e06e6
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/ProfileAliasDataFetcher.java
@@ -0,0 +1,41 @@
+/*
+ * 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.unomi.graphql.fetchers.profile;
+
+import graphql.schema.DataFetcher;
+import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.ProfileAlias;
+import org.apache.unomi.graphql.services.ServiceManager;
+import org.apache.unomi.graphql.types.output.CDPProfileAlias;
+import org.apache.unomi.persistence.spi.PersistenceService;
+
+public class ProfileAliasDataFetcher implements DataFetcher<CDPProfileAlias> {
+
+    private final String alias;
+
+    public ProfileAliasDataFetcher(final String alias) {
+        this.alias = alias;
+    }
+
+    @Override
+    public CDPProfileAlias get(final DataFetchingEnvironment environment) throws Exception {
+        ServiceManager serviceManager = environment.getContext();
+        PersistenceService persistenceService = serviceManager.getService(PersistenceService.class);
+        ProfileAlias profileAlias = persistenceService.load(alias, ProfileAlias.class);
+        return profileAlias != null ? new CDPProfileAlias(profileAlias) : null;
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPProfileAliasFilterInput.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPProfileAliasFilterInput.java
new file mode 100644
index 000000000..ace68007a
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPProfileAliasFilterInput.java
@@ -0,0 +1,83 @@
+/*
+ * 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.unomi.graphql.types.input;
+
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLName;
+
+import java.util.List;
+
+import static org.apache.unomi.graphql.types.input.CDPProfileAliasFilterInput.TYPE_NAME;
+
+@GraphQLName(TYPE_NAME)
+public class CDPProfileAliasFilterInput {
+
+    public static final String TYPE_NAME = "CDP_ProfileAliasFilterInput";
+
+    @GraphQLField
+    @GraphQLName("and")
+    private final List<CDPProfileAliasFilterInput> and;
+
+    @GraphQLField
+    @GraphQLName("or")
+    private final List<CDPProfileAliasFilterInput> or;
+
+    @GraphQLField
+    @GraphQLName("alias_equals")
+    private final String alias_equals;
+
+    @GraphQLField
+    @GraphQLName("profileID_equals")
+    private final String profileID_equals;
+
+    @GraphQLField
+    @GraphQLName("clientID_equals")
+    private final String clientID_equals;
+
+    public CDPProfileAliasFilterInput(
+            @GraphQLName("and") List<CDPProfileAliasFilterInput> and,
+            @GraphQLName("or") List<CDPProfileAliasFilterInput> or,
+            @GraphQLName("alias_equals") String alias_equals,
+            @GraphQLName("profileID_equals") String profileID_equals,
+            @GraphQLName("clientID_equals") String clientID_equals) {
+        this.and = and;
+        this.or = or;
+        this.alias_equals = alias_equals;
+        this.profileID_equals = profileID_equals;
+        this.clientID_equals = clientID_equals;
+    }
+
+    public List<CDPProfileAliasFilterInput> getAnd() {
+        return and;
+    }
+
+    public List<CDPProfileAliasFilterInput> getOr() {
+        return or;
+    }
+
+    public String getAlias_equals() {
+        return alias_equals;
+    }
+
+    public String getProfileID_equals() {
+        return profileID_equals;
+    }
+
+    public String getClientID_equals() {
+        return clientID_equals;
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPProfileAliasInput.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPProfileAliasInput.java
new file mode 100644
index 000000000..1351a8cce
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/input/CDPProfileAliasInput.java
@@ -0,0 +1,53 @@
+/*
+ * 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.unomi.graphql.types.input;
+
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLID;
+import graphql.annotations.annotationTypes.GraphQLName;
+import graphql.annotations.annotationTypes.GraphQLNonNull;
+
+import static org.apache.unomi.graphql.types.input.CDPProfileAliasInput.TYPE_NAME;
+
+@GraphQLName(TYPE_NAME)
+public class CDPProfileAliasInput {
+
+    public static final String TYPE_NAME = "CDP_ProfileAliasInput";
+
+    @GraphQLID
+    @GraphQLField
+    @GraphQLNonNull
+    private final String alias;
+
+    @GraphQLField
+    @GraphQLNonNull
+    private final CDPProfileIDInput profileID;
+
+    public CDPProfileAliasInput(@GraphQLID @GraphQLNonNull @GraphQLName("alias") String alias,
+                                @GraphQLNonNull @GraphQLName("profileID") CDPProfileIDInput profileID) {
+        this.alias = alias;
+        this.profileID = profileID;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public CDPProfileIDInput getProfileID() {
+        return profileID;
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPMutation.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPMutation.java
index ee1cdeb49..26f88bdd1 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPMutation.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPMutation.java
@@ -21,6 +21,7 @@ import graphql.annotations.annotationTypes.GraphQLID;
 import graphql.annotations.annotationTypes.GraphQLName;
 import graphql.annotations.annotationTypes.GraphQLNonNull;
 import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.graphql.commands.AddAliasToProfileCommand;
 import org.apache.unomi.graphql.commands.CreateOrUpdatePersonaCommand;
 import org.apache.unomi.graphql.commands.CreateOrUpdateProfilePropertiesCommand;
 import org.apache.unomi.graphql.commands.CreateOrUpdateSourceCommand;
@@ -33,6 +34,7 @@ import org.apache.unomi.graphql.commands.DeleteSourceCommand;
 import org.apache.unomi.graphql.commands.DeleteTopicCommand;
 import org.apache.unomi.graphql.commands.DeleteViewCommand;
 import org.apache.unomi.graphql.commands.ProcessEventsCommand;
+import org.apache.unomi.graphql.commands.RemoveAliasFromProfileCommand;
 import org.apache.unomi.graphql.commands.list.AddProfileToListCommand;
 import org.apache.unomi.graphql.commands.list.CreateOrUpdateListCommand;
 import org.apache.unomi.graphql.commands.list.DeleteListCommand;
@@ -43,6 +45,7 @@ import org.apache.unomi.graphql.commands.segments.DeleteSegmentCommand;
 import org.apache.unomi.graphql.types.input.CDPEventInput;
 import org.apache.unomi.graphql.types.input.CDPListInput;
 import org.apache.unomi.graphql.types.input.CDPPersonaInput;
+import org.apache.unomi.graphql.types.input.CDPProfileAliasInput;
 import org.apache.unomi.graphql.types.input.CDPProfileIDInput;
 import org.apache.unomi.graphql.types.input.CDPPropertyInput;
 import org.apache.unomi.graphql.types.input.CDPSegmentInput;
@@ -265,4 +268,28 @@ public class CDPMutation {
                 .execute();
     }
 
+    @GraphQLField
+    public CDPProfileAlias addAliasToProfile(
+            final @GraphQLNonNull @GraphQLName("profileAlias") CDPProfileAliasInput profileAliasInput,
+            final DataFetchingEnvironment environment) {
+        return AddAliasToProfileCommand.create()
+                .profileAliasInput(profileAliasInput)
+                .setEnvironment(environment)
+                .build()
+                .execute();
+    }
+
+    @GraphQLField
+    public CDPProfileAlias removeAliasFromProfile(
+            final @GraphQLID @GraphQLNonNull @GraphQLName("alias") String alias,
+            final @GraphQLName("profileID") CDPProfileIDInput profileIDInput,
+            final DataFetchingEnvironment environment
+    ) {
+        return RemoveAliasFromProfileCommand.create()
+                .setAlias(alias)
+                .setProfileIDInput(profileIDInput)
+                .setEnvironment(environment)
+                .build()
+                .execute();
+    }
 }
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAlias.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAlias.java
new file mode 100644
index 000000000..cf2b3bbfa
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAlias.java
@@ -0,0 +1,66 @@
+/*
+ * 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.unomi.graphql.types.output;
+
+import graphql.annotations.annotationTypes.GraphQLDescription;
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLName;
+import org.apache.unomi.api.ProfileAlias;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+
+import static org.apache.unomi.graphql.types.output.CDPProfileAlias.TYPE_NAME;
+
+@GraphQLName(TYPE_NAME)
+@GraphQLDescription("Alias for Profile.")
+public class CDPProfileAlias {
+    public static final String TYPE_NAME = "CDP_ProfileAlias";
+
+    private final ProfileAlias profileAlias;
+
+    public CDPProfileAlias(final ProfileAlias profileAlias) {
+        this.profileAlias = profileAlias;
+    }
+
+    @GraphQLField
+    public String alias() {
+        return profileAlias != null ? profileAlias.getItemId() : null;
+    }
+
+    @GraphQLField
+    public CDPProfileID profileID() {
+        if (profileAlias != null) {
+            CDPProfileID profileID = new CDPProfileID(profileAlias.getProfileID());
+            if (profileAlias.getClientID() != null) {
+                profileID.setClient(new CDPClient(profileAlias.getClientID(), profileAlias.getClientID()));
+            }
+            return profileID;
+        }
+        return null;
+    }
+
+    @GraphQLField
+    public OffsetDateTime creationTime() {
+        return profileAlias != null ? profileAlias.getCreationTime().toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime() : null;
+    }
+
+    @GraphQLField
+    public OffsetDateTime modifiedTime() {
+        return profileAlias != null ? profileAlias.getModifiedTime().toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime() : null;
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAliasConnection.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAliasConnection.java
new file mode 100644
index 000000000..a6a9459b1
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAliasConnection.java
@@ -0,0 +1,66 @@
+/*
+ * 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.unomi.graphql.types.output;
+
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLName;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.unomi.graphql.types.output.CDPProfileAliasConnection.TYPE_NAME;
+
+@GraphQLName(TYPE_NAME)
+public class CDPProfileAliasConnection {
+
+    public static final String TYPE_NAME = "CDP_ProfileAliasConnection";
+
+    private final List<CDPProfileAliasEdge> edges;
+
+    private final CDPPageInfo pageInfo;
+
+    private final Long totalCount;
+
+    public CDPProfileAliasConnection() {
+        this(new ArrayList<>(), new CDPPageInfo(), 0L);
+    }
+
+    public CDPProfileAliasConnection(
+            final List<CDPProfileAliasEdge> edges, final CDPPageInfo pageInfo, final Long totalCount) {
+        this.edges = edges;
+        this.pageInfo = pageInfo;
+        this.totalCount = totalCount;
+    }
+
+    @GraphQLField
+    @GraphQLName("edges")
+    public List<CDPProfileAliasEdge> edges() {
+        return edges;
+    }
+
+    @GraphQLField
+    @GraphQLName("pageInfo")
+    public CDPPageInfo pageInfo() {
+        return pageInfo;
+    }
+
+    @GraphQLField
+    @GraphQLName("totalCount")
+    public Long totalCount() {
+        return totalCount;
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAliasEdge.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAliasEdge.java
new file mode 100644
index 000000000..293b7678f
--- /dev/null
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPProfileAliasEdge.java
@@ -0,0 +1,49 @@
+/*
+ * 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.unomi.graphql.types.output;
+
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLName;
+import graphql.annotations.annotationTypes.GraphQLNonNull;
+import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.ProfileAlias;
+
+import static org.apache.unomi.graphql.types.output.CDPProfileAliasEdge.TYPE_NAME;
+
+@GraphQLName(TYPE_NAME)
+public class CDPProfileAliasEdge {
+
+    public static final String TYPE_NAME = "CDP_ProfileAliasEdge";
+
+    private final ProfileAlias profileAlias;
+
+    public CDPProfileAliasEdge(final ProfileAlias profileAlias) {
+        this.profileAlias = profileAlias;
+    }
+
+    @GraphQLField
+    @GraphQLName("node")
+    public CDPProfileAlias node(final DataFetchingEnvironment environment) {
+        return new CDPProfileAlias(profileAlias);
+    }
+
+    @GraphQLNonNull
+    @GraphQLField
+    public String cursor() {
+        return profileAlias != null ? profileAlias.getItemId() : null;
+    }
+}
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPQuery.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPQuery.java
index e723fcf17..2b7ba0d1e 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPQuery.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/types/output/CDPQuery.java
@@ -16,7 +16,11 @@
  */
 package org.apache.unomi.graphql.types.output;
 
-import graphql.annotations.annotationTypes.*;
+import graphql.annotations.annotationTypes.GraphQLDescription;
+import graphql.annotations.annotationTypes.GraphQLField;
+import graphql.annotations.annotationTypes.GraphQLID;
+import graphql.annotations.annotationTypes.GraphQLName;
+import graphql.annotations.annotationTypes.GraphQLNonNull;
 import graphql.schema.DataFetchingEnvironment;
 import org.apache.unomi.graphql.fetchers.FindTopicsConnectionDataFetcher;
 import org.apache.unomi.graphql.fetchers.SourceDataFetcher;
@@ -26,7 +30,10 @@ import org.apache.unomi.graphql.fetchers.event.EventDataFetcher;
 import org.apache.unomi.graphql.fetchers.event.FindEventsConnectionDataFetcher;
 import org.apache.unomi.graphql.fetchers.list.GetListDataFetcher;
 import org.apache.unomi.graphql.fetchers.list.ListConnectionDataFetcher;
+import org.apache.unomi.graphql.fetchers.profile.FindProfileAliasConnectionDataFetcher;
 import org.apache.unomi.graphql.fetchers.profile.FindProfilesConnectionDataFetcher;
+import org.apache.unomi.graphql.fetchers.profile.GetProfileAliasesDataFetcher;
+import org.apache.unomi.graphql.fetchers.profile.ProfileAliasDataFetcher;
 import org.apache.unomi.graphql.fetchers.profile.ProfileDataFetcher;
 import org.apache.unomi.graphql.fetchers.profile.PropertiesConnectionDataFetcher;
 import org.apache.unomi.graphql.fetchers.segment.FindSegmentsConnectionDataFetcher;
@@ -35,6 +42,7 @@ import org.apache.unomi.graphql.fetchers.segment.UnomiSegmentDataFetcher;
 import org.apache.unomi.graphql.types.input.CDPEventFilterInput;
 import org.apache.unomi.graphql.types.input.CDPListFilterInput;
 import org.apache.unomi.graphql.types.input.CDPOrderByInput;
+import org.apache.unomi.graphql.types.input.CDPProfileAliasFilterInput;
 import org.apache.unomi.graphql.types.input.CDPProfileFilterInput;
 import org.apache.unomi.graphql.types.input.CDPProfileIDInput;
 import org.apache.unomi.graphql.types.input.CDPSegmentFilterInput;
@@ -174,4 +182,30 @@ public class CDPQuery {
         return new ListConnectionDataFetcher(filterInput, orderByInput).get(environment);
     }
 
+    @GraphQLField
+    public CDPProfileAlias getProfileAlias(
+            final @GraphQLID @GraphQLNonNull @GraphQLName("alias") String alias,
+            final DataFetchingEnvironment environment) throws Exception {
+        return new ProfileAliasDataFetcher(alias).get(environment);
+    }
+
+    @GraphQLField
+    public List<CDPProfileAlias> getProfileAliases(
+            final @GraphQLID @GraphQLName("profileID") @GraphQLNonNull String profileID,
+            final DataFetchingEnvironment environment) throws Exception {
+        return new GetProfileAliasesDataFetcher(profileID).get(environment);
+    }
+
+    @GraphQLField
+    public CDPProfileAliasConnection findProfileAliases(
+            final @GraphQLName("filter") CDPProfileAliasFilterInput filterInput,
+            final @GraphQLName("orderBy") List<CDPOrderByInput> orderByInput,
+            final @GraphQLName("first") Integer first,
+            final @GraphQLName("after") String after,
+            final @GraphQLName("last") Integer last,
+            final @GraphQLName("before") String before,
+            final DataFetchingEnvironment environment
+    ) throws Exception {
+        return new FindProfileAliasConnectionDataFetcher(filterInput, orderByInput).get(environment);
+    }
 }
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
index e9bf22105..b91aec295 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
@@ -29,6 +29,8 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
+
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.Before;
@@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.UUID;
 import java.util.stream.IntStream;
 
 import javax.inject.Inject;
@@ -179,7 +182,8 @@ public class ProfileServiceIT extends BaseIT {
 
     @Test
     public void testLoadProfileByAlias() throws Exception {
-        String profileID = "profileID_testLoadProfileByAlias";
+        String profileID = UUID.randomUUID().toString();
+
         try {
             Profile profile = new Profile();
             profile.setItemId(profileID);
@@ -189,7 +193,7 @@ public class ProfileServiceIT extends BaseIT {
 
             IntStream.range(1, 3).forEach(index -> {
                 final String profileAlias = profileID + "_alias_" + index;
-                profileService.addAliasToProfile(profileID, profileAlias, "clientID");
+                profileService.addAliasToProfile(profileID, profileAlias, "clientID" + index);
             });
 
             refreshPersistence();
@@ -205,13 +209,16 @@ public class ProfileServiceIT extends BaseIT {
             storedProfile = profileService.load(profileID + "_alias_2");
             assertNotNull(storedProfile);
             assertEquals(profileID, storedProfile.getItemId());
-        } finally {
-            profileService.delete(profileID, false);
 
+            PartialList<ProfileAlias> aliasList = profileService.findProfileAliases(profileID, 0, 10, null);
+            assertEquals(2, aliasList.size());
+        } finally {
             IntStream.range(1, 3).forEach(index -> {
                 final String profileAlias = profileID + "_alias_" + index;
-                persistenceService.remove(profileAlias, ProfileAlias.class);
+                profileService.removeAliasFromProfile(profileID, profileAlias, "clientID" + index);
             });
+
+            profileService.delete(profileID, false);
         }
     }
 
diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLProfileAliasesIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLProfileAliasesIT.java
new file mode 100644
index 000000000..6cd62759e
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLProfileAliasesIT.java
@@ -0,0 +1,104 @@
+/*
+ * 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.unomi.itests.graphql;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.ProfileAlias;
+import org.apache.unomi.api.services.ProfileService;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.ops4j.pax.exam.util.Filter;
+
+import javax.inject.Inject;
+
+public class GraphQLProfileAliasesIT extends BaseGraphQLIT {
+
+    @Inject
+    @Filter(timeout = 600000)
+    protected ProfileService profileService;
+
+    @Before
+    public void setUp() throws InterruptedException {
+        removeItems(ProfileAlias.class);
+        removeItems(Profile.class);
+    }
+
+    @Test
+    public void lifecycle() throws Exception {
+        final Profile profile = new Profile("f6c1c5a0-eff3-42b7-b375-44da5d01f2a6");
+        profileService.save(profile);
+
+        // test adding an alias to a profile
+        try (CloseableHttpResponse response = post("graphql/profileAlias/addAliasToProfile.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            Assert.assertNotNull(context.getValue("data.cdp.addAliasToProfile"));
+            Assert.assertEquals("myAlias", context.getValue("data.cdp.addAliasToProfile.alias"));
+            Assert.assertNotNull(context.getValue("data.cdp.addAliasToProfile.profileID"));
+            Assert.assertEquals("f6c1c5a0-eff3-42b7-b375-44da5d01f2a6",
+                    context.getValue("data.cdp.addAliasToProfile.profileID.id"));
+            Assert.assertNotNull(context.getValue("data.cdp.addAliasToProfile.profileID.client"));
+            Assert.assertEquals("facebook", context.getValue("data.cdp.addAliasToProfile.profileID.client.ID"));
+        }
+
+        // test fetching a profile by an alias
+        try (CloseableHttpResponse response = post("graphql/profileAlias/getProfileAlias.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            Assert.assertNotNull(context.getValue("data.cdp.getProfileAlias"));
+            Assert.assertEquals("myAlias", context.getValue("data.cdp.getProfileAlias.alias"));
+            Assert.assertNotNull(context.getValue("data.cdp.getProfileAlias.profileID"));
+            Assert.assertEquals("f6c1c5a0-eff3-42b7-b375-44da5d01f2a6",
+                    context.getValue("data.cdp.getProfileAlias.profileID.id"));
+        }
+
+        // test fetching a profile's aliases
+        try (CloseableHttpResponse response = post("graphql/profileAlias/getProfileAliases.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            Assert.assertNotNull(context.getValue("data.cdp.getProfileAliases"));
+            Assert.assertEquals("myAlias", context.getValue("data.cdp.getProfileAliases[0].alias"));
+            Assert.assertNotNull(context.getValue("data.cdp.getProfileAliases[0].profileID"));
+            Assert.assertEquals("f6c1c5a0-eff3-42b7-b375-44da5d01f2a6",
+                    context.getValue("data.cdp.getProfileAliases[0].profileID.id"));
+        }
+
+        // test filtering a profile aliases by criteria
+        try (CloseableHttpResponse response = post("graphql/profileAlias/findProfileAliases.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            Assert.assertNotNull(context.getValue("data.cdp.findProfileAliases"));
+            Assert.assertEquals("myAlias", context.getValue("data.cdp.findProfileAliases.edges[0].node.alias"));
+            Assert.assertNotNull(context.getValue("data.cdp.findProfileAliases.edges[0].node.profileID"));
+            Assert.assertEquals("f6c1c5a0-eff3-42b7-b375-44da5d01f2a6",
+                    context.getValue("data.cdp.findProfileAliases.edges[0].node.profileID.id"));
+        }
+
+        // test removing an alias from a profile
+        try (CloseableHttpResponse response = post("graphql/profileAlias/removeAliasFromProfile.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            Assert.assertNotNull(context.getValue("data.cdp.removeAliasFromProfile"));
+            Assert.assertEquals("myAlias", context.getValue("data.cdp.removeAliasFromProfile.alias"));
+            Assert.assertNotNull(context.getValue("data.cdp.removeAliasFromProfile.profileID"));
+            Assert.assertEquals("f6c1c5a0-eff3-42b7-b375-44da5d01f2a6",
+                    context.getValue("data.cdp.removeAliasFromProfile.profileID.id"));
+        }
+    }
+}
diff --git a/itests/src/test/resources/graphql/profileAlias/addAliasToProfile.json b/itests/src/test/resources/graphql/profileAlias/addAliasToProfile.json
new file mode 100644
index 000000000..81a2f7e0a
--- /dev/null
+++ b/itests/src/test/resources/graphql/profileAlias/addAliasToProfile.json
@@ -0,0 +1,16 @@
+{
+  "operationName": "addAliasToProfile",
+  "variables": {
+    "profileAlias": {
+      "alias": "myAlias",
+      "profileID": {
+        "id": "f6c1c5a0-eff3-42b7-b375-44da5d01f2a6",
+        "client": {
+          "id": "facebook",
+          "title": "Facebook"
+        }
+      }
+    }
+  },
+  "query": "mutation addAliasToProfile($profileAlias: CDP_ProfileAliasInput!) {\n  cdp {\n    addAliasToProfile(profileAlias: $profileAlias) {\n      alias\n      profileID {\n        client {\n          ID\n          title\n        }\n        id\n      }\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/profileAlias/findProfileAliases.json b/itests/src/test/resources/graphql/profileAlias/findProfileAliases.json
new file mode 100644
index 000000000..5e4abddc6
--- /dev/null
+++ b/itests/src/test/resources/graphql/profileAlias/findProfileAliases.json
@@ -0,0 +1,8 @@
+{
+  "operationName": null,
+  "variables": {
+    "alias": "myAlias",
+    "clientID": "facebook"
+  },
+  "query": "query ($alias: String, $clientID: String) {\n  cdp {\n    findProfileAliases(filter: {and: [{alias_equals: $alias}, {clientID_equals: $clientID}]}) {\n      totalCount\n      edges {\n        cursor\n        node {\n          alias\n          profileID {\n            id\n            client {\n              ID\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/profileAlias/getProfileAlias.json b/itests/src/test/resources/graphql/profileAlias/getProfileAlias.json
new file mode 100644
index 000000000..8c972904d
--- /dev/null
+++ b/itests/src/test/resources/graphql/profileAlias/getProfileAlias.json
@@ -0,0 +1,5 @@
+{
+  "operationName": null,
+  "variables": {},
+  "query": "{\n  cdp {\n    getProfileAlias(alias: \"myAlias\") {\n      alias\n      creationTime\n      modifiedTime\n      profileID {\n        id\n      }\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/profileAlias/getProfileAliases.json b/itests/src/test/resources/graphql/profileAlias/getProfileAliases.json
new file mode 100644
index 000000000..32f4a7885
--- /dev/null
+++ b/itests/src/test/resources/graphql/profileAlias/getProfileAliases.json
@@ -0,0 +1,5 @@
+{
+  "operationName": null,
+  "variables": {},
+  "query": "{\n  cdp {\n    getProfileAliases(profileID: \"f6c1c5a0-eff3-42b7-b375-44da5d01f2a6\") {\n      alias\n      creationTime\n      modifiedTime\n      profileID {\n        client {\n          ID\n          title\n        }\n        id\n      }\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/profileAlias/removeAliasFromProfile.json b/itests/src/test/resources/graphql/profileAlias/removeAliasFromProfile.json
new file mode 100644
index 000000000..915dba2c6
--- /dev/null
+++ b/itests/src/test/resources/graphql/profileAlias/removeAliasFromProfile.json
@@ -0,0 +1,13 @@
+{
+  "operationName": "removeAliasFromProfile",
+  "variables": {
+    "alias": "myAlias",
+    "profileID": {
+      "id": "f6c1c5a0-eff3-42b7-b375-44da5d01f2a6",
+      "client": {
+        "id": "facebook"
+      }
+    }
+  },
+  "query": "mutation removeAliasFromProfile($alias: ID!, $profileID: CDP_ProfileIDInput!) {\n  cdp {\n    removeAliasFromProfile(alias: $alias, profileID: $profileID) {\n      alias\n      creationTime\n      modifiedTime\n      profileID {\n        id\n        client {\n          ID\n          title\n        }\n      }\n    }\n  }\n}\n"
+}
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java
index db6aa93db..86b50b714 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java
@@ -622,4 +622,31 @@ public class ProfileServiceEndPoint {
     public PartialList<Session> searchSession(Query query) {
         return profileService.searchSessions(query);
     }
+
+    @POST
+    @Path("/{profileId}/aliases/{aliasId}")
+    public void addAliasToProfile(final @PathParam("profileId") String profileId,
+                                  final @PathParam("aliasId") String aliasId,
+                                  final @HeaderParam("X-Unomi-ClientId") String headerClientID) {
+        String clientId = headerClientID != null ? headerClientID : "defaultClientId";
+        profileService.addAliasToProfile(profileId, aliasId, clientId);
+    }
+
+    @DELETE
+    @Path("/{profileId}/aliases/{aliasId}")
+    public void removeAliasFromProfile(final @PathParam("profileId") String profileId,
+                                       final @PathParam("aliasId") String aliasId,
+                                       final @HeaderParam("X-Unomi-ClientId") String headerClientID) {
+        String clientId = headerClientID != null ? headerClientID : "defaultClientId";
+        profileService.removeAliasFromProfile(profileId, aliasId, clientId);
+    }
+
+    @GET
+    @Path("/{profileId}/aliases")
+    public PartialList<ProfileAlias> listAliasesByProfileId(final @PathParam("profileId") String profileId,
+                                                            @QueryParam("offset") @DefaultValue("0") int offset,
+                                                            @QueryParam("size") @DefaultValue("50") int size,
+                                                            @QueryParam("sort") String sortBy) {
+        return profileService.findProfileAliases(profileId, offset, size, sortBy);
+    }
 }
diff --git a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
index 148a98d96..4903074af 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
@@ -581,6 +581,51 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList
         }
     }
 
+    @Override
+    public ProfileAlias removeAliasFromProfile(String profileID, String alias, String clientID) {
+        Condition profileIDCondition = new Condition(definitionsService.getConditionType("profileAliasesPropertyCondition"));
+        profileIDCondition.setParameter("propertyName", "profileID.keyword");
+        profileIDCondition.setParameter("comparisonOperator", "equals");
+        profileIDCondition.setParameter("propertyValue", profileID);
+
+        Condition clientIDCondition = new Condition(definitionsService.getConditionType("profileAliasesPropertyCondition"));
+        clientIDCondition.setParameter("propertyName", "clientID.keyword");
+        clientIDCondition.setParameter("comparisonOperator", "equals");
+        clientIDCondition.setParameter("propertyValue", clientID);
+
+        Condition aliasCondition = new Condition(definitionsService.getConditionType("profileAliasesPropertyCondition"));
+        aliasCondition.setParameter("propertyName", "itemId");
+        aliasCondition.setParameter("comparisonOperator", "equals");
+        aliasCondition.setParameter("propertyValue", alias);
+
+        List<Condition> conditions = new ArrayList<>();
+        conditions.add(profileIDCondition);
+        conditions.add(clientIDCondition);
+        conditions.add(aliasCondition);
+
+        Condition condition = new Condition(definitionsService.getConditionType("booleanCondition"));
+        condition.setParameter("operator", "and");
+        condition.setParameter("subConditions", conditions);
+
+        List<ProfileAlias> profileAliases = persistenceService.query(condition, null, ProfileAlias.class);
+
+        if (profileAliases.size() == 1 && persistenceService.removeByQuery(condition, ProfileAlias.class)) {
+            return profileAliases.get(0);
+        }
+
+        return null;
+    }
+
+    @Override
+    public PartialList<ProfileAlias> findProfileAliases(String profileId, int offset, int size, String sortBy) {
+        Condition condition = new Condition(definitionsService.getConditionType("profileAliasesPropertyCondition"));
+        condition.setParameter("propertyName", "profileID.keyword");
+        condition.setParameter("comparisonOperator", "equals");
+        condition.setParameter("propertyValue", profileId);
+
+        return persistenceService.query(condition, sortBy, ProfileAlias.class, offset, size);
+    }
+
     private Profile save(Profile profile, boolean forceRefresh) {
         if (profile.getItemId() == null) {
             return null;