Added delete-group-by-name controller & unique index to group name.

Change-Id: I463de3b56cbbd8e66df6be8dac5a3ce4a3b35b78
diff --git a/full/Changes b/full/Changes
index c27dcc5..e8ba17d 100644
--- a/full/Changes
+++ b/full/Changes
@@ -2,7 +2,9 @@
 04/02/2019
    - Fixed SQL data and merged oauth2_client_url and oauth2_client (margaretha)
    - Updated client deregistration behavior (margaretha)
- 
+05/02/2019
+   - Added delete-group-by-name controller (margaretha)
+   - Added unique index to group name (margaretha)
 
 # version 0.61.5
 17/12/2018
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java b/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
index 3705719..d3e8d47 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
+++ b/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
@@ -39,6 +39,7 @@
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private int id;
+    // unique
     @Column(name = "name")
     private String name;
     @Column(name = "created_by")
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 b45114d..ef44901 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
@@ -203,13 +203,15 @@
                 UserGroupStatus.ACTIVE);
         UserGroup userGroup = userGroupDao.retrieveGroupById(groupId);
 
-        for (String memberId : groupJson.getMembers()) {
-            if (memberId.equals(createdBy)) {
-                // skip owner, already added while creating group.
-                continue;
+        if (groupJson.getMembers() != null){
+            for (String memberId : groupJson.getMembers()) {
+                if (memberId.equals(createdBy)) {
+                    // skip owner, already added while creating group.
+                    continue;
+                }
+                inviteGroupMember(memberId, userGroup, createdBy,
+                        GroupMemberStatus.PENDING);
             }
-            inviteGroupMember(memberId, userGroup, createdBy,
-                    GroupMemberStatus.PENDING);
         }
     }
 
@@ -232,6 +234,27 @@
                     "Unauthorized operation for user: " + username, username);
         }
     }
+    
+    public void deleteGroup (String groupName, String username)
+            throws KustvaktException {
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName);
+        if (userGroup.getStatus() == UserGroupStatus.DELETED) {
+            // EM: should this be "not found" instead?
+            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(userGroup.getId(), username,
+                    config.isSoftDeleteGroup());
+        }
+        else {
+            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                    "Unauthorized operation for user: " + username, username);
+        }
+    }
 
     public int createAutoHiddenGroup (int vcId) throws KustvaktException {
         String groupName = "auto-hidden-group";
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 86104e6..e287be6 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
@@ -207,6 +207,22 @@
             throw kustvaktResponseHandler.throwit(e);
         }
     }
+    
+    @DELETE
+    @Path("{groupName}")
+    public Response deleteUserGroupByName (@Context SecurityContext securityContext,
+            @PathParam("groupName") String groupName) {
+        TokenContext context =
+                (TokenContext) securityContext.getUserPrincipal();
+        try {
+            scopeService.verifyScope(context, OAuth2Scope.DELETE_USER_GROUP);
+            service.deleteGroup(groupName, context.getUsername());
+            return Response.ok().build();
+        }
+        catch (KustvaktException e) {
+            throw kustvaktResponseHandler.throwit(e);
+        }
+    }
 
     /**
      * Deletes a user-group member. Group owner cannot be deleted.
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java
index 71f0bff..a4d7303 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java
@@ -41,7 +41,7 @@
  *
  */
 @Controller
-@Path("{version}/{username: ~[a-zA-Z0-9_]+}/setting")
+@Path("{version}/{username: ~[a-zA-Z0-9_.]+}/setting")
 @ResourceFilters({ AuthenticationFilter.class, APIVersionFilter.class,
         PiwikFilter.class })
 public class UserSettingController {
@@ -55,7 +55,7 @@
 
     /**
      * Creates a default setting of the given username.
-     * The setting inputs should be represented as a pair of keys and
+     * The setting inputs should be represented as pairs of keys and
      * values (a map). The keys must only contains alphabets, numbers,
      * hypens or underscores.
      * 
diff --git a/full/src/main/resources/db/mysql/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/mysql/V1.1__create_virtual_corpus_tables.sql
index eb9d1df..ac1f81d 100644
--- a/full/src/main/resources/db/mysql/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/mysql/V1.1__create_virtual_corpus_tables.sql
@@ -22,7 +22,8 @@
   status varchar(100) NOT NULL,
   created_by varchar(100) NOT NULL,
   deleted_by varchar(100) DEFAULT NULL,
-  INDEX status_index(status)
+  INDEX status_index(status),
+  UNIQUE INDEX unique_name(name);
 );
 
 CREATE TABLE IF NOT EXISTS user_group_member (
diff --git a/full/src/main/resources/db/sqlite/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/sqlite/V1.1__create_virtual_corpus_tables.sql
index 0547591..0198da2 100644
--- a/full/src/main/resources/db/sqlite/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/sqlite/V1.1__create_virtual_corpus_tables.sql
@@ -26,6 +26,7 @@
 );
 
 CREATE INDEX user_group_index ON user_group(status);
+CREATE UNIQUE INDEX user_group_name on user_group(name);
 
 
 CREATE TABLE IF NOT EXISTS user_group_member (
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 86d902e..60ffcf8 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
@@ -58,6 +58,17 @@
 
         return JsonUtils.readTree(entity);
     }
+    
+    private void deleteGroupByName (String groupName) throws KustvaktException{
+        ClientResponse response = resource().path(API_VERSION).path("group")
+                .path(groupName)
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .delete(ClientResponse.class);
+        
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
 
     // dory is a group admin in dory group
     @Test
@@ -122,7 +133,6 @@
                 .path("list").header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .get(ClientResponse.class);
         String entity = response.getEntity(String.class);
-        // System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
 
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
@@ -132,8 +142,49 @@
                 node.at("/errors/0/1").asText());
     }
 
+    
     @Test
-    public void testCreateUserGroup () throws UniformInterfaceException,
+    public void testCreateGroupEmptyMembers () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        String groupName = "empty_group";
+        UserGroupJson json = new UserGroupJson();
+        json.setName(groupName);
+        json.setMembers(new String[] {});
+
+        ClientResponse response = resource().path(API_VERSION).path("group")
+                .path("create").type(MediaType.APPLICATION_JSON)
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").entity(json)
+                .post(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+     
+        deleteGroupByName(groupName);
+    }
+
+    
+    @Test
+    public void testCreateGroupMissingMembers () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        String groupName = "missing-member-group";
+        UserGroupJson json = new UserGroupJson();
+        json.setName(groupName);
+
+        ClientResponse response = resource().path(API_VERSION).path("group")
+                .path("create").type(MediaType.APPLICATION_JSON)
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").entity(json)
+                .post(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        
+        deleteGroupByName(groupName);
+    }
+    
+    @Test
+    public void testUserGroup () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
 
         UserGroupJson json = new UserGroupJson();
@@ -287,6 +338,8 @@
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
+        // EM: this is so complicated because the group retrieval are not allowed 
+        // for delete groups
         // check group
         response = resource().path(API_VERSION).path("group").path("list")
                 .path("system-admin").queryParam("username", username)
@@ -295,16 +348,17 @@
                         .createBasicAuthorizationHeaderValue(admin, "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);
-
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(1, node.size());
-        assertEquals(groupId, node.at("/0/id").asText());
-
-        // check group members
-        for (int i = 0; i < node.at("/0/members").size(); i++) {
-            assertEquals(GroupMemberStatus.DELETED.name(),
-                    node.at("/0/members/" + i + "/status").asText());
+        for (int j = 0; j < node.size(); j++){
+            JsonNode group = node.get(j);
+            // check group members
+            for (int i = 0; i < group.at("/0/members").size(); i++) {
+                assertEquals(GroupMemberStatus.DELETED.name(),
+                        group.at("/0/members/" + i + "/status").asText());
+            }
         }
     }
 
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserSettingControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserSettingControllerTest.java
index 7ac81d2..29c5890 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserSettingControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserSettingControllerTest.java
@@ -28,8 +28,8 @@
  */
 public class UserSettingControllerTest extends SpringJerseyTest {
 
-    private String username = "UserSettingTest";
-    private String username2 = "UserSettingTest2";
+    private String username = "UserSetting_Test";
+    private String username2 = "UserSetting.Test2";
 
     public ClientResponse sendPutRequest (String username,
             Map<String, Object> map) throws KustvaktException {
diff --git a/lite/Changes b/lite/Changes
index 8a068e2..74d334c 100644
--- a/lite/Changes
+++ b/lite/Changes
@@ -1,3 +1,7 @@
+version 0.61.4
+05/02/2019
+  - Updated kustvakt-lite.conf
+
 version 0.61.3
 19/12/2018
   - Fixed getUser in DummyAuthenticationManager (margaretha)
diff --git a/lite/src/main/resources/kustvakt-lite.conf b/lite/src/main/resources/kustvakt-lite.conf
index db385f0..5636857 100644
--- a/lite/src/main/resources/kustvakt-lite.conf
+++ b/lite/src/main/resources/kustvakt-lite.conf
@@ -1,3 +1,5 @@
+# Krill settings
+
 # index dir
 krill.indexDir= ../sample-index
 
@@ -6,20 +8,25 @@
 krill.index.commit.auto = 500
 krill.index.relations.max = 100
 
-krill.namedVC=vc
+# krill.namedVC=vc
 
-# Kustvakt
+
+# Kustvakt settings
 
 current.api.version = v1.0
 # multiple versions separated by space
 supported.api.version = v1.0
 
+# default
 kustvakt.base.url=/api/*
-kustvakt.default.pos = tt
-kustvakt.default.lemma = tt
-kustvakt.default.token = opennlp
-kustvakt.default.dep = mate
-kustvakt.default.const = mate
+
+# default foundries for layers
+default.foundry.partOfSpeech = tt
+default.foundry.lemma = tt
+default.foundry.orthography = opennlp
+default.foundry.dependency = malt
+default.foundry.constituent = corenlp
+default.foundry.morphology = marmot
 
 # server
 server.port=8089