Updated group api paths & replaced groupId with groupName (resolved #33)

Change-Id: I78da6099cf19c0107eaa82b5bade404053e444b5
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index bd4bf89..2116102 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -122,6 +122,7 @@
     public static final int GROUP_MEMBER_DELETED = 1603;
     public static final int GROUP_MEMBER_NOT_FOUND = 1604;
     public static final int INVITATION_EXPIRED = 1605;
+    @Deprecated
     public static final int GROUP_NOT_FOUND = 1606;
     public static final int GROUP_DELETED = 1607;
     public static final int GROUP_EXISTS = 1608;
diff --git a/full/Changes b/full/Changes
index 0256d49..b8b6e88 100644
--- a/full/Changes
+++ b/full/Changes
@@ -3,6 +3,9 @@
    - Handled vulnerability CVE-2019-17195. (margaretha)
 8/11/2019
    - Added user-group name pattern (margaretha, issue #33)
+11/11/2019
+   - Updated user group service paths and replaced groupId with groupName 
+     (margaretha, resolved #33)
 
 # version 0.62.1
 08/07/2019
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
index 358ba99..28b8650 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
@@ -208,7 +208,7 @@
         }
     }
 
-    public UserGroup retrieveGroupByName (String groupName)
+    public UserGroup retrieveGroupByName (String groupName, boolean fetchMembers)
             throws KustvaktException {
         ParameterChecker.checkStringValue(groupName, "groupName");
 
@@ -217,6 +217,9 @@
                 criteriaBuilder.createQuery(UserGroup.class);
 
         Root<UserGroup> root = query.from(UserGroup.class);
+        if (fetchMembers) {
+            root.fetch(UserGroup_.members);
+        }
         query.select(root);
         query.where(
                 criteriaBuilder.equal(root.get(UserGroup_.name), groupName));
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java b/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
index 8eee9a4..78dcd55 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
@@ -135,7 +135,7 @@
     
     public UserGroup retrieveUserGroupByName (String groupName)
             throws KustvaktException {
-        return userGroupDao.retrieveGroupByName(groupName);
+        return userGroupDao.retrieveGroupByName(groupName, false);
     }
 
     public UserGroup retrieveHiddenUserGroupByVC (int vcId)
@@ -227,7 +227,7 @@
         }
         
         try{
-            userGroupDao.retrieveGroupByName(groupJson.getName());
+            userGroupDao.retrieveGroupByName(groupJson.getName(),false);
             throw new KustvaktException(StatusCodes.GROUP_EXISTS,
                     "User-group name must be unique.");
         }
@@ -271,29 +271,9 @@
         }
     }
 
-    public void deleteGroup (int groupId, String username)
-            throws KustvaktException {
-        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId);
-        if (userGroup.getStatus() == UserGroupStatus.DELETED) {
-            throw new KustvaktException(StatusCodes.GROUP_DELETED,
-                    "Group " + userGroup.getName() + " has been deleted.",
-                    userGroup.getName());
-        }
-        else if (userGroup.getCreatedBy().equals(username)
-                || adminDao.isAdmin(username)) {
-            // soft delete
-            userGroupDao.deleteGroup(groupId, username,
-                    config.isSoftDeleteGroup());
-        }
-        else {
-            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
-                    "Unauthorized operation for user: " + username, username);
-        }
-    }
-    
     public void deleteGroup (String groupName, String username)
             throws KustvaktException {
-        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName);
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName,false);
         if (userGroup.getStatus() == UserGroupStatus.DELETED) {
             // EM: should this be "not found" instead?
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
@@ -312,7 +292,7 @@
         }
     }
 
-    public int createAutoHiddenGroup (int vcId) throws KustvaktException {
+    public int createAutoHiddenGroup () throws KustvaktException {
         String code = random.createRandomCode();
         String groupName = "auto-"+code;
         int groupId = userGroupDao.createGroup(groupName, "system",
@@ -411,14 +391,13 @@
         return null;
     }
 
-    public void inviteGroupMembers (UserGroupJson group, String inviter)
-            throws KustvaktException {
-        int groupId = group.getId();
+    public void inviteGroupMembers (UserGroupJson group, String groupName,
+            String inviter) throws KustvaktException {
         String[] members = group.getMembers();
-        ParameterChecker.checkIntegerValue(groupId, "id");
+        ParameterChecker.checkStringValue(groupName, "group name");
         ParameterChecker.checkObjectValue(members, "members");
 
-        UserGroup userGroup = retrieveUserGroupById(groupId);
+        UserGroup userGroup = retrieveUserGroupByName(groupName);
         if (userGroup.getStatus() == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
                     "Group " + userGroup.getName() + " has been deleted.",
@@ -463,13 +442,13 @@
      *            the username of the group member
      * @throws KustvaktException
      */
-    public void acceptInvitation (int groupId, String username)
+    public void acceptInvitation (String groupName, String username)
             throws KustvaktException {
 
         ParameterChecker.checkStringValue(username, "userId");
-        ParameterChecker.checkIntegerValue(groupId, "groupId");
+        ParameterChecker.checkStringValue(groupName, "groupId");
 
-        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId);
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName, false);
         if (userGroup.getStatus() == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
                     "Group " + userGroup.getName() + " has been deleted.",
@@ -477,7 +456,7 @@
         }
 
         UserGroupMember member =
-                groupMemberDao.retrieveMemberById(username, groupId);
+                groupMemberDao.retrieveMemberById(username, userGroup.getId());
         GroupMemberStatus status = member.getStatus();
         if (status.equals(GroupMemberStatus.DELETED)) {
             throw new KustvaktException(StatusCodes.GROUP_MEMBER_DELETED,
@@ -528,10 +507,10 @@
         return false;
     }
 
-    public void deleteGroupMember (String memberId, int groupId,
+    public void deleteGroupMember (String memberId, String groupName,
             String deletedBy) throws KustvaktException {
 
-        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId);
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName, false);
         if (userGroup.getStatus() == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
                     "Group " + userGroup.getName() + " has been deleted.",
@@ -546,7 +525,7 @@
                 || isUserGroupAdmin(deletedBy, userGroup)
                 || adminDao.isAdmin(deletedBy)) {
             // soft delete
-            doDeleteMember(memberId, groupId, deletedBy,
+            doDeleteMember(memberId, userGroup.getId(), deletedBy,
                     config.isSoftDeleteGroupMember());
         }
         else {
@@ -588,10 +567,11 @@
         groupMemberDao.deleteMember(member, deletedBy, isSoftDelete);
     }
 
-    public UserGroupDto searchById (String username, int groupId)
+    public UserGroupDto searchByName (String username, String groupName)
             throws KustvaktException {
         if (adminDao.isAdmin(username)) {
-            UserGroup userGroup = userGroupDao.retrieveGroupById(groupId, true);
+            UserGroup userGroup =
+                    userGroupDao.retrieveGroupByName(groupName, true);
             UserGroupDto groupDto = converter.createUserGroupDto(userGroup,
                     userGroup.getMembers(), null, null);
             return groupDto;
@@ -603,15 +583,15 @@
 
     }
 
-    public void editMemberRoles (String username, int groupId,
+    public void editMemberRoles (String username, String groupName,
             String memberUsername, List<Integer> roleIds)
             throws KustvaktException {
 
-        ParameterChecker.checkIntegerValue(groupId, "groupId");
         ParameterChecker.checkStringValue(username, "username");
+        ParameterChecker.checkStringValue(groupName, "groupName");
         ParameterChecker.checkStringValue(memberUsername, "memberUsername");
 
-        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId, true);
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName, true);
         UserGroupStatus groupStatus = userGroup.getStatus();
         if (groupStatus == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
@@ -621,7 +601,8 @@
                 || adminDao.isAdmin(username)) {
 
             UserGroupMember member =
-                    groupMemberDao.retrieveMemberById(memberUsername, groupId);
+                    groupMemberDao.retrieveMemberById(memberUsername,
+                            userGroup.getId());
 
             if (!member.getStatus().equals(GroupMemberStatus.ACTIVE)) {
                 throw new KustvaktException(StatusCodes.GROUP_MEMBER_INACTIVE,
@@ -643,15 +624,15 @@
         }
     }
 
-    public void addMemberRoles (String username, int groupId,
+    public void addMemberRoles (String username, String groupName,
             String memberUsername, List<Integer> roleIds)
             throws KustvaktException {
 
-        ParameterChecker.checkIntegerValue(groupId, "groupId");
         ParameterChecker.checkStringValue(username, "username");
+        ParameterChecker.checkStringValue(groupName, "groupName");
         ParameterChecker.checkStringValue(memberUsername, "memberUsername");
 
-        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId, true);
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName, true);
         UserGroupStatus groupStatus = userGroup.getStatus();
         if (groupStatus == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
@@ -661,7 +642,8 @@
                 || adminDao.isAdmin(username)) {
 
             UserGroupMember member =
-                    groupMemberDao.retrieveMemberById(memberUsername, groupId);
+                    groupMemberDao.retrieveMemberById(memberUsername,
+                            userGroup.getId());
 
             if (!member.getStatus().equals(GroupMemberStatus.ACTIVE)) {
                 throw new KustvaktException(StatusCodes.GROUP_MEMBER_INACTIVE,
@@ -683,21 +665,22 @@
         }
     }
 
-    public void deleteMemberRoles (String username, int groupId,
+    public void deleteMemberRoles (String username, String groupName,
             String memberUsername, List<Integer> roleIds)
             throws KustvaktException {
 
-        ParameterChecker.checkIntegerValue(groupId, "groupId");
         ParameterChecker.checkStringValue(username, "username");
+        ParameterChecker.checkStringValue(groupName, "groupName");
         ParameterChecker.checkStringValue(memberUsername, "memberUsername");
 
-        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId, true);
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName, true);
 
         if (isUserGroupAdmin(username, userGroup)
                 || adminDao.isAdmin(username)) {
 
             UserGroupMember member =
-                    groupMemberDao.retrieveMemberById(memberUsername, groupId);
+                    groupMemberDao.retrieveMemberById(memberUsername,
+                            userGroup.getId());
 
             Set<Role> roles = member.getRoles();
             Iterator<Role> i = roles.iterator();
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java b/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
index 48d2a62..4ebb07a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
@@ -301,7 +301,7 @@
         if (access == null) {
             VirtualCorpus vc = vcDao.retrieveVCById(vcId);
             // create and assign a new hidden group
-            int groupId = userGroupService.createAutoHiddenGroup(vcId);
+            int groupId = userGroupService.createAutoHiddenGroup();
             UserGroup autoHidden =
                     userGroupService.retrieveUserGroupById(groupId);
             accessDao.createAccessToVC(vc, autoHidden, "system",
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
index 4f66530..013b61a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
@@ -63,8 +63,8 @@
     /**
      * Returns all user-groups in which a user is an active or a
      * pending member.
-     * Not suitable for system-admin, instead use
-     * {@link UserGroupController#
+     * 
+     * Not suitable for system-admin, instead use {@link UserGroupController#
      * getUserGroupBySystemAdmin(SecurityContext, String, UserGroupStatus)}
      * 
      * @param securityContext
@@ -119,24 +119,25 @@
     }
 
     /**
-     * Retrieves a specific user-group for system admins.
+     * Retrieves a specific user-group. Only system admins are
+     * allowed.
      * 
      * @param securityContext
-     * @param groupId
-     *            group id
+     * @param groupName
+     *            group name
      * @return a user-group
      */
     @GET
-    @Path("{groupId}")
+    @Path("{groupName}")
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
-    public UserGroupDto searchUserGroup (
+    public UserGroupDto retrieveUserGroup (
             @Context SecurityContext securityContext,
-            @PathParam("groupId") int groupId) {
+            @PathParam("groupName") String groupName) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
             scopeService.verifyScope(context, OAuth2Scope.ADMIN);
-            return service.searchById(context.getUsername(), groupId);
+            return service.searchByName(context.getUsername(), groupName);
         }
         catch (KustvaktException e) {
             throw kustvaktResponseHandler.throwit(e);
@@ -187,29 +188,12 @@
      * owner and system admins can delete groups.
      * 
      * @param securityContext
-     * @param groupId
+     * @param groupName the name of the group to delete
      * @return HTTP 200, if successful.
      */
     @DELETE
-    @Path("delete/{groupId}")
-    public Response deleteUserGroup (@Context SecurityContext securityContext,
-            @PathParam("groupId") int groupId) {
-        TokenContext context =
-                (TokenContext) securityContext.getUserPrincipal();
-        try {
-            scopeService.verifyScope(context, OAuth2Scope.DELETE_USER_GROUP);
-            service.deleteGroup(groupId, context.getUsername());
-            return Response.ok().build();
-        }
-        catch (KustvaktException e) {
-            throw kustvaktResponseHandler.throwit(e);
-        }
-    }
-
-    @DELETE
     @Path("{groupName}")
-    public Response deleteUserGroupByName (
-            @Context SecurityContext securityContext,
+    public Response deleteUserGroup (@Context SecurityContext securityContext,
             @PathParam("groupName") String groupName) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
@@ -223,28 +207,32 @@
         }
     }
 
+
     /**
-     * Deletes a user-group member. Group owner cannot be deleted.
+     * Removes a user-group member. Group owner cannot be deleted.
+     * Only group admins, system admins and the member himself can
+     * remove a member. 
      * 
      * @param securityContext
-     * @param memberId
+     * @param memberUsername
      *            a username of a group member
-     * @param groupId
-     *            a group id
+     * @param groupName
+     *            a group name
      * @return if successful, HTTP response status OK
      */
     @DELETE
-    @Path("member/delete/{groupId}/{memberId}")
-    public Response deleteUserFromGroup (
+    @Path("{groupName}/{memberUsername}")
+    public Response removeUserFromGroup (
             @Context SecurityContext securityContext,
-            @PathParam("memberId") String memberId,
-            @PathParam("groupId") int groupId) {
+            @PathParam("memberUsername") String memberUsername,
+            @PathParam("groupName") String groupName) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.DELETE_USER_GROUP_MEMBER);
-            service.deleteGroupMember(memberId, groupId, context.getUsername());
+            service.deleteGroupMember(memberUsername, groupName,
+                    context.getUsername());
             return Response.ok().build();
         }
         catch (KustvaktException e) {
@@ -259,22 +247,23 @@
      * 
      * @param securityContext
      * @param group
-     *            UserGroupJson containing groupId and usernames to be
-     *            invited
-     *            as members
+     *            UserGroupJson containing groupName and usernames to be
+     *            invited as members
      * @return if successful, HTTP response status OK
      */
     @POST
-    @Path("member/invite")
+    @Path("{groupName}/invite")
     @Consumes(MediaType.APPLICATION_JSON)
     public Response inviteGroupMembers (
-            @Context SecurityContext securityContext, UserGroupJson group) {
+            @Context SecurityContext securityContext,
+            @PathParam("groupName") String groupName,
+            UserGroupJson group) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.ADD_USER_GROUP_MEMBER);
-            service.inviteGroupMembers(group, context.getUsername());
+            service.inviteGroupMembers(group, groupName, context.getUsername());
             return Response.ok("SUCCESS").build();
         }
         catch (KustvaktException e) {
@@ -287,16 +276,16 @@
      * as well.
      * 
      * @param securityContext
-     * @param groupId
+     * @param groupName
      * @param memberUsername
      * @param roleIds
      * @return
      */
     @POST
-    @Path("member/role/edit")
+    @Path("{groupName}/role/edit")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response editMemberRoles (@Context SecurityContext securityContext,
-            @FormParam("groupId") int groupId,
+            @PathParam("groupName") String groupName,
             @FormParam("memberUsername") String memberUsername,
             @FormParam("roleIds") List<Integer> roleIds) {
         TokenContext context =
@@ -304,7 +293,7 @@
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.EDIT_USER_GROUP_MEMBER_ROLE);
-            service.editMemberRoles(context.getUsername(), groupId,
+            service.editMemberRoles(context.getUsername(), groupName,
                     memberUsername, roleIds);
             return Response.ok("SUCCESS").build();
         }
@@ -318,19 +307,19 @@
      * admins and system admins are allowed.
      * 
      * @param securityContext
-     * @param groupId
-     *            a group id
+     * @param groupName
+     *            a group name
      * @param memberUsername
-     *            the username of a group member
+     *            a username of a group member
      * @param roleIds
      *            list of role ids
      * @return if successful, HTTP response status OK
      */
     @POST
-    @Path("member/role/add")
+    @Path("{groupName}/role/add")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response addMemberRoles (@Context SecurityContext securityContext,
-            @FormParam("groupId") int groupId,
+            @PathParam("groupName") String groupName,
             @FormParam("memberUsername") String memberUsername,
             @FormParam("roleIds") List<Integer> roleIds) {
         TokenContext context =
@@ -338,7 +327,7 @@
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.ADD_USER_GROUP_MEMBER_ROLE);
-            service.addMemberRoles(context.getUsername(), groupId,
+            service.addMemberRoles(context.getUsername(), groupName,
                     memberUsername, roleIds);
             return Response.ok("SUCCESS").build();
         }
@@ -348,23 +337,23 @@
     }
 
     /**
-     * Deletes roles of a member of a user-group. Only user-group
-     * admins and system admins are allowed.
+     * Updates the roles of a member of a user-group by removing the
+     * given roles. Only user-group admins and system admins are allowed.
      * 
      * @param securityContext
-     * @param groupId
-     *            a group id
+     * @param groupName
+     *            a group name
      * @param memberUsername
-     *            the username of a group member
+     *            a username of a group member
      * @param roleIds
      *            list of role ids
      * @return if successful, HTTP response status OK
      */
     @POST
-    @Path("member/role/delete")
+    @Path("{groupName}/role/delete")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response deleteMemberRoles (@Context SecurityContext securityContext,
-            @FormParam("groupId") int groupId,
+            @PathParam("groupName") String groupName,
             @FormParam("memberUsername") String memberUsername,
             @FormParam("roleIds") List<Integer> roleIds) {
         TokenContext context =
@@ -372,7 +361,7 @@
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.DELETE_USER_GROUP_MEMBER_ROLE);
-            service.deleteMemberRoles(context.getUsername(), groupId,
+            service.deleteMemberRoles(context.getUsername(), groupName,
                     memberUsername, roleIds);
             return Response.ok("SUCCESS").build();
         }
@@ -386,21 +375,20 @@
      * users can subscribe to the corresponding user-group.
      * 
      * @param securityContext
-     * @param groupId
-     *            a group id
+     * @param groupName
+     *            a group name
      * @return if successful, HTTP response status OK
      */
     @POST
-    @Path("subscribe")
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Path("{groupName}/subscribe")
     public Response subscribeToGroup (@Context SecurityContext securityContext,
-            @FormParam("groupId") int groupId) {
+            @PathParam("groupName") String groupName) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.ADD_USER_GROUP_MEMBER);
-            service.acceptInvitation(groupId, context.getUsername());
+            service.acceptInvitation(groupName, context.getUsername());
             return Response.ok("SUCCESS").build();
         }
         catch (KustvaktException e) {
@@ -412,24 +400,23 @@
      * Handles requests to reject membership invitation. A member can
      * only unsubscribe him/herself from a group.
      * 
-     * Implemented identical to delete group member.
+     * Implemented identical to {@link #removeUserFromGroup(SecurityContext, String, String)}.
      * 
      * @param securityContext
-     * @param groupId
+     * @param groupName
      * @return if successful, HTTP response status OK
      */
-    @POST
-    @Path("unsubscribe")
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @DELETE
+    @Path("{groupName}/unsubscribe")
     public Response unsubscribeFromGroup (
             @Context SecurityContext securityContext,
-            @FormParam("groupId") int groupId) {
+            @PathParam("groupName") String groupName) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.DELETE_USER_GROUP_MEMBER);
-            service.deleteGroupMember(context.getUsername(), groupId,
+            service.deleteGroupMember(context.getUsername(), groupName,
                     context.getUsername());
             return Response.ok("SUCCESS").build();
         }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java b/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java
index b08e85f..f89743e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java
@@ -14,6 +14,7 @@
 @Setter
 public class UserGroupJson {
 
+    @Deprecated
     private int id;
     private String name;
     private String[] members;
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
index cabc5da..1f1d874 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
@@ -92,12 +92,14 @@
     @Test
     public void testListWithoutUsername () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
-        ClientResponse response = resource().path(API_VERSION).path("group").path("list").header(
-                Attributes.AUTHORIZATION,
-                HttpAuthorizationHandler.createBasicAuthorizationHeaderValue(
-                        adminUsername, "pass"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .get(ClientResponse.class);
+        ClientResponse response =
+                resource().path(API_VERSION).path("group").path("list")
+                        .header(Attributes.AUTHORIZATION,
+                                HttpAuthorizationHandler
+                                        .createBasicAuthorizationHeaderValue(
+                                                adminUsername, "pass"))
+                        .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                        .get(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         String entity = response.getEntity(String.class);
@@ -157,7 +159,8 @@
         json.setName(groupName);
         json.setMembers(new String[] { "marlin", "nemo" });
 
-        ClientResponse response = resource().path(API_VERSION).path("group").path("create")
+        ClientResponse response = resource().path(API_VERSION).path("group")
+                .path("create")
                 .type(MediaType.APPLICATION_JSON)
                 .header(Attributes.AUTHORIZATION,
                         HttpAuthorizationHandler
@@ -174,23 +177,22 @@
         node = node.get(0);
         assertEquals(groupName, node.get("name").asText());
 
-        String groupId = node.get("id").asText();
-        testMemberRole("marlin", groupId);
-        testInviteMember(groupId);
-        testDeleteMember(groupId);
-        testDeleteGroup(groupId);
+        testMemberRole("marlin", groupName);
+        testInviteMember(groupName);
+        testDeleteMember(groupName);
+        testDeleteGroup(groupName);
     }
 
-    private void testMemberRole (String memberUsername, String groupId)
+    private void testMemberRole (String memberUsername, String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
 
         // accept invitation
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", groupId);
+        form.add("groupName", groupName);
 
-        ClientResponse response = resource().path(API_VERSION).path("group").path("subscribe")
-                .type(MediaType.APPLICATION_FORM_URLENCODED)
+        ClientResponse response = resource().path(API_VERSION).path("group")
+                .path(groupName).path("subscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
@@ -198,21 +200,21 @@
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
-        testAddMemberRoles(groupId, memberUsername);
-        testDeleteMemberRoles(groupId, memberUsername);
+        testAddMemberRoles(groupName, memberUsername);
+        testDeleteMemberRoles(groupName, memberUsername);
     }
 
-    private void testAddMemberRoles (String groupId, String memberUsername)
+    private void testAddMemberRoles (String groupName, String memberUsername)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         MultivaluedMap<String, String> map = new MultivaluedMapImpl();
-        map.add("groupId", groupId.toString());
         map.add("memberUsername", memberUsername);
         map.add("roleIds", "1"); // USER_GROUP_ADMIN
         map.add("roleIds", "2"); // USER_GROUP_MEMBER
 
         ClientResponse response =
-                resource().path(API_VERSION).path("group").path("member").path("role").path("add")
+                resource().path(API_VERSION).path("group").path(groupName)
+                        .path("role").path("add")
                         .type(MediaType.APPLICATION_FORM_URLENCODED)
                         .header(Attributes.AUTHORIZATION,
                                 HttpAuthorizationHandler
@@ -223,7 +225,7 @@
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
-        JsonNode node = retrieveGroup(groupId).at("/members");
+        JsonNode node = retrieveGroup(groupName).at("/members");
         JsonNode member;
         for (int i = 0; i < node.size(); i++) {
             member = node.get(i);
@@ -236,15 +238,14 @@
         }
     }
 
-    private void testDeleteMemberRoles (String groupId, String memberUsername)
+    private void testDeleteMemberRoles (String groupName, String memberUsername)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         MultivaluedMap<String, String> map = new MultivaluedMapImpl();
-        map.add("groupId", groupId.toString());
         map.add("memberUsername", memberUsername);
         map.add("roleIds", "1"); // USER_GROUP_ADMIN
 
-        ClientResponse response = resource().path(API_VERSION).path("group").path("member")
+        ClientResponse response = resource().path(API_VERSION).path("group").path(groupName)
                 .path("role").path("delete")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(Attributes.AUTHORIZATION,
@@ -256,7 +257,7 @@
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
-        JsonNode node = retrieveGroup(groupId).at("/members");
+        JsonNode node = retrieveGroup(groupName).at("/members");
         JsonNode member;
         for (int i = 0; i < node.size(); i++) {
             member = node.get(i);
@@ -267,12 +268,12 @@
         }
     }
 
-    private JsonNode retrieveGroup (String groupId)
+    private JsonNode retrieveGroup (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
-        ClientResponse response = resource().path(API_VERSION).path("group").path(groupId).header(
-                Attributes.AUTHORIZATION,
-                HttpAuthorizationHandler.createBasicAuthorizationHeaderValue(
+        ClientResponse response = resource().path(API_VERSION).path("group").path(groupName)
+                .header(Attributes.AUTHORIZATION,
+                        HttpAuthorizationHandler.createBasicAuthorizationHeaderValue(
                         adminUsername, "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .get(ClientResponse.class);
@@ -284,12 +285,12 @@
         return node;
     }
 
-    private void testDeleteGroup (String groupId)
+    private void testDeleteGroup (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         // delete group
         ClientResponse response =
-                resource().path(API_VERSION).path("group").path("delete").path(groupId)
+                resource().path(API_VERSION).path("group").path(groupName)
                         .header(Attributes.AUTHORIZATION,
                                 HttpAuthorizationHandler
                                         .createBasicAuthorizationHeaderValue(
@@ -304,12 +305,12 @@
         assertEquals(0, node.size());
     }
 
-    private void testDeleteMember (String groupId)
+    private void testDeleteMember (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         // delete marlin from group
-        ClientResponse response = resource().path(API_VERSION).path("group").path("member")
-                .path("delete").path(groupId).path("marlin")
+        ClientResponse response = resource().path(API_VERSION).path("group")
+                .path(groupName).path("marlin")
                 .header(Attributes.AUTHORIZATION,
                         HttpAuthorizationHandler
                                 .createBasicAuthorizationHeaderValue(
@@ -328,17 +329,16 @@
                 node.at("/members/1/status").asText());
     }
 
-    private void testInviteMember (String groupId)
+    private void testInviteMember (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         String[] members = new String[] { "darla" };
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
-        userGroup.setId(Integer.parseInt(groupId));
 
-        ClientResponse response = resource().path(API_VERSION).path("group").path("member")
-                .path("invite").type(MediaType.APPLICATION_JSON)
+        ClientResponse response = resource().path(API_VERSION).path("group")
+                .path(groupName).path("invite").type(MediaType.APPLICATION_JSON)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION,
                         HttpAuthorizationHandler
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
index 52880e7..d538065 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
@@ -253,22 +253,22 @@
                 node.at("/members/1/status").asText());
         assertEquals(0, node.at("/members/1/roles").size());
 
-        testInviteMember(groupId);
+        testInviteMember(groupName);
 
-        testDeleteMemberUnauthorized(groupId);
-        testDeleteMember(groupId);
-        testDeleteGroup(groupId);
+        testDeleteMemberUnauthorized(groupName);
+        testDeleteMember(groupName);
+        testDeleteGroup(groupName);
 
-        testSubscribeToDeletedGroup(groupId);
-        testUnsubscribeToDeletedGroup(groupId);
+        testSubscribeToDeletedGroup(groupName);
+        testUnsubscribeToDeletedGroup(groupName);
     }
 
-    private void testDeleteMember (String groupId)
+    private void testDeleteMember (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         // delete marlin from group
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("delete").path(groupId).path("marlin")
+                .path(groupName).path("marlin")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue(username, "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -286,12 +286,12 @@
         assertEquals(3, node.get("members").size());
     }
 
-    private void testDeleteMemberUnauthorized (String groupId)
+    private void testDeleteMemberUnauthorized (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         // nemo is a group member
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("delete").path(groupId).path("marlin")
+                .path(groupName).path("marlin")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("nemo", "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -312,9 +312,7 @@
             ClientHandlerException, KustvaktException {
         // dory delete pearl
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member")
-                // dory-group
-                .path("delete").path("2").path("pearl")
+                .path("dory-group").path("pearl")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -331,7 +329,7 @@
     public void testDeleteDeletedMember () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("delete").path("2").path("pearl")
+                .path("dory-group").path("pearl")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -348,12 +346,12 @@
         assertEquals("[pearl, dory-group]", node.at("/errors/0/2").asText());
     }
 
-    private void testDeleteGroup (String groupId)
+    private void testDeleteGroup (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         // delete group
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("delete").path(groupId)
+                .path(groupName)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue(username, "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -388,9 +386,9 @@
     @Test
     public void testDeleteGroupUnauthorized () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
-        // dory is a group admin in marlin group
+        // dory is a group admin in marlin-group
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("delete").path("1")
+                .path("marlin-group")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -410,7 +408,7 @@
     public void testDeleteDeletedGroup () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("delete").path("4")
+                .path("deleted-group")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -429,10 +427,10 @@
     @Test
     public void testDeleteGroupOwner () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
-        // delete marlin from marlin group
-        // dory is a group admin in marlin group
+        // delete marlin from marlin-group
+        // dory is a group admin in marlin-group
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("delete").path("1").path("marlin")
+                .path("marlin-group").path("marlin")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -447,17 +445,16 @@
                 node.at("/errors/0/1").asText());
     }
 
-    private void testInviteMember (String groupId)
+    private void testInviteMember (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         String[] members = new String[] { "darla" };
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
-        userGroup.setId(Integer.parseInt(groupId));
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("invite").type(MediaType.APPLICATION_JSON)
+                .path(groupName).path("invite").type(MediaType.APPLICATION_JSON)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue(username, "pass"))
@@ -490,11 +487,9 @@
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
-        // dory group
-        userGroup.setId(2);
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("invite").type(MediaType.APPLICATION_JSON)
+                .path("dory-group").path("invite").type(MediaType.APPLICATION_JSON)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
@@ -519,11 +514,9 @@
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
-        // dory group
-        userGroup.setId(2);
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("invite").type(MediaType.APPLICATION_JSON)
+                .path("dory-group").path("invite").type(MediaType.APPLICATION_JSON)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
@@ -549,11 +542,9 @@
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
-        // dory group
-        userGroup.setId(2);
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("invite").type(MediaType.APPLICATION_JSON)
+                .path("dory-group").path("invite").type(MediaType.APPLICATION_JSON)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
@@ -580,11 +571,9 @@
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
-        // dory group
-        userGroup.setId(2);
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("invite").type(MediaType.APPLICATION_JSON)
+                .path("dory-group").path("invite").type(MediaType.APPLICATION_JSON)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
@@ -612,11 +601,9 @@
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
-        // dory's deleted group
-        userGroup.setId(4);
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("invite").type(MediaType.APPLICATION_JSON)
+                .path("deleted-group").path("invite").type(MediaType.APPLICATION_JSON)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("dory", "pass"))
@@ -635,15 +622,12 @@
     // marlin has GroupMemberStatus.PENDING in dory-group
     @Test
     public void testSubscribePendingMember () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "2");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("subscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("dory-group").path("subscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .post(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
@@ -666,8 +650,8 @@
                 group.at("/userRoles/1").asText());
 
         // unsubscribe marlin from dory-group
-        testUnsubscribeActiveMember(form);
-        checkGroupMemberRole("2", "marlin");
+        testUnsubscribeActiveMember("dory-group");
+        checkGroupMemberRole("dory-group", "marlin");
 
         // invite marlin to dory-group to set back the
         // GroupMemberStatus.PENDING
@@ -677,15 +661,12 @@
     // pearl has GroupMemberStatus.DELETED in dory-group
     @Test
     public void testSubscribeDeletedMember () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "2");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("subscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("dory-group").path("subscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("pearl", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .post(ClientResponse.class);
         String entity = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
 
@@ -697,34 +678,25 @@
     }
 
     @Test
-    public void testSubscribeMissingGroupId () throws KustvaktException {
+    public void testSubscribeMissingGroupName() throws KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("group")
                 .path("subscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("bruce", "pass"))
                 .post(ClientResponse.class);
-        String entity = response.getEntity(String.class);
-        JsonNode node = JsonUtils.readTree(entity);
-
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.MISSING_PARAMETER,
-                node.at("/errors/0/0").asInt());
-        assertEquals("groupId is missing", node.at("/errors/0/1").asText());
-        assertEquals("groupId", node.at("/errors/0/2").asText());
+        assertEquals(Status.METHOD_NOT_ALLOWED.getStatusCode(),
+                response.getStatus());
     }
 
     @Test
     public void testSubscribeNonExistentMember () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "2");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("subscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("dory-group").path("subscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("bruce", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .post(ClientResponse.class);
         String entity = response.getEntity(String.class);
         // System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
@@ -738,38 +710,31 @@
 
     @Test
     public void testSubscribeToNonExistentGroup () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "100");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("subscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("non-existent").path("subscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("pearl", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .post(ClientResponse.class);
         String entity = response.getEntity(String.class);
-        // System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
 
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.GROUP_NOT_FOUND,
+        assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
+        assertEquals(StatusCodes.NO_RESOURCE_FOUND,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Group with id 100 is not found",
+        assertEquals("Group non-existent is not found",
                 node.at("/errors/0/1").asText());
     }
 
-    private void testSubscribeToDeletedGroup (String groupId)
+    private void testSubscribeToDeletedGroup (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", groupId);
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("subscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path(groupName).path("subscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("nemo", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .post(ClientResponse.class);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -780,16 +745,15 @@
                 node.at("/errors/0/1").asText());
     }
 
-    private void testUnsubscribeActiveMember (
-            MultivaluedMap<String, String> form)
+    private void testUnsubscribeActiveMember (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("unsubscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path(groupName).path("unsubscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .delete(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
@@ -797,10 +761,10 @@
         assertEquals(1, node.size());
     }
 
-    private void checkGroupMemberRole (String groupId, String deletedMemberName)
+    private void checkGroupMemberRole (String groupName, String deletedMemberName)
             throws KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path(groupId)
+                .path(groupName)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue(admin, "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
@@ -825,16 +789,12 @@
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         // pearl unsubscribes from dory-group
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        // dory-group
-        form.add("groupId", "2");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("unsubscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("dory-group").path("unsubscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("pearl", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .delete(ClientResponse.class);
 
         String entity = response.getEntity(String.class);
         // System.out.println(entity);
@@ -855,16 +815,12 @@
         JsonNode node = retrieveUserGroups("marlin");
         assertEquals(2, node.size());
 
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        // dory-group
-        form.add("groupId", "2");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("unsubscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("dory-group").path("unsubscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .delete(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
@@ -877,38 +833,25 @@
     }
 
     @Test
-    public void testUnsubscribeMissingGroupId () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-
+    public void testUnsubscribeMissingGroupName () throws KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("unsubscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("unsubscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .delete(ClientResponse.class);
 
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-
-        String entity = response.getEntity(String.class);
-        JsonNode node = JsonUtils.readTree(entity);
-
-        assertEquals(StatusCodes.MISSING_PARAMETER,
-                node.at("/errors/0/0").asInt());
-        assertEquals("groupId is missing", node.at("/errors/0/1").asText());
-        assertEquals("groupId", node.at("/errors/0/2").asText());
+        assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
     }
 
     @Test
     public void testUnsubscribeNonExistentMember () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "2");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("unsubscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("dory-group").path("unsubscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("bruce", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .delete(ClientResponse.class);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -923,39 +866,34 @@
 
     @Test
     public void testUnsubscribeToNonExistentGroup () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "100");
-
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("unsubscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path("tralala-group").path("unsubscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("pearl", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .delete(ClientResponse.class);
 
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
 
         String entity = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
 
-        assertEquals(StatusCodes.GROUP_NOT_FOUND,
+        assertEquals(StatusCodes.NO_RESOURCE_FOUND,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Group with id 100 is not found",
+        assertEquals("Group tralala-group is not found",
                 node.at("/errors/0/1").asText());
     }
 
-    private void testUnsubscribeToDeletedGroup (String groupId)
+    private void testUnsubscribeToDeletedGroup (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", groupId);
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("unsubscribe").type(MediaType.APPLICATION_FORM_URLENCODED)
+                .path(groupName).path("unsubscribe")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("nemo", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .delete(ClientResponse.class);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -970,12 +908,11 @@
     public void testAddSameMemberRole () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "1");
         form.add("memberUsername", "dory");
         form.add("roleIds", "1");
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("role").path("add")
+                .path("marlin-group").path("role").path("add")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
@@ -992,12 +929,11 @@
     public void testDeleteAddMemberRole () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "1");
         form.add("memberUsername", "dory");
         form.add("roleIds", "1");
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("role").path("delete")
+                .path("marlin-group").path("role").path("delete")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
@@ -1016,15 +952,14 @@
     public void testEditMemberRoleEmpty () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("groupId", "1");
         form.add("memberUsername", "dory");
-
+        
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("role").path("edit")
+                .path("marlin-group").path("role").path("edit")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .entity(form).post(ClientResponse.class);
+                .post(ClientResponse.class, form);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
@@ -1032,17 +967,19 @@
         Set<Role> roles = member.getRoles();
         assertEquals(0, roles.size());
 
-        testEditMemberRole(form);
+        testEditMemberRole();
     }
 
-    private void testEditMemberRole (MultivaluedMap<String, String> form)
+    private void testEditMemberRole ()
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("memberUsername", "dory");
         form.add("roleIds", "1");
         form.add("roleIds", "3");
 
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path("member").path("role").path("edit")
+                .path("marlin-group").path("role").path("edit")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
index 297d3ab..13e1ec9 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
@@ -353,28 +353,28 @@
         assertTrue(node.at("/userGroupName").asText().startsWith("auto"));
         assertEquals(vcName, node.at("/vcName").asText());
 
-        String groupId = node.at("/userGroupId").asText();
+        String groupName = node.at("/userGroupName").asText();
 
         // EM: check if hidden group has been created
-        node = testCheckHiddenGroup(groupId);
+        node = testCheckHiddenGroup(groupName);
         assertEquals("HIDDEN", node.at("/status").asText());
 
         // EM: delete vc
         testDeleteVC(vcName, "VirtualCorpusControllerTest");
 
         // EM: check if the hidden groups are deleted as well
-        node = testCheckHiddenGroup(groupId);
-        assertEquals(StatusCodes.GROUP_NOT_FOUND,
+        node = testCheckHiddenGroup(groupName);
+        assertEquals(StatusCodes.NO_RESOURCE_FOUND,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Group with id " + groupId + " is not found",
+        assertEquals("Group "+ groupName + " is not found",
                 node.at("/errors/0/1").asText());
     }
 
-    private JsonNode testCheckHiddenGroup (String groupId)
+    private JsonNode testCheckHiddenGroup (String groupName)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("group")
-                .path(groupId)
+                .path(groupName)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue("admin", "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")