Handled unique constraints.

Change-Id: I0ee968b18b028ded76af04a0473c7997a13f2f11
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/NamedVCLoader.java b/full/src/main/java/de/ids_mannheim/korap/config/NamedVCLoader.java
index fc86f52..21216a8 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/NamedVCLoader.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/NamedVCLoader.java
@@ -16,11 +16,8 @@
 
 import de.ids_mannheim.korap.KrillCollection;
 import de.ids_mannheim.korap.constant.VirtualCorpusType;
-import de.ids_mannheim.korap.dao.VirtualCorpusDao;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.service.VirtualCorpusService;
-import de.ids_mannheim.korap.user.User.CorpusAccess;
 import de.ids_mannheim.korap.util.QueryException;
 import de.ids_mannheim.korap.web.SearchKrill;
 
@@ -31,8 +28,6 @@
     @Autowired
     private SearchKrill searchKrill;
     @Autowired
-    private VirtualCorpusDao vcDao;
-    @Autowired
     private VirtualCorpusService vcService;
 
     private static Logger jlog = LogManager.getLogger(NamedVCLoader.class);
@@ -44,7 +39,8 @@
         String json = IOUtils.toString(is, "utf-8");
         if (json != null) {
             cacheVC(json, filename);
-            storeVC(filename, json);
+            vcService.storeVC(filename, VirtualCorpusType.SYSTEM, json, null,
+                    null, null, true, "system");
         }
     }
 
@@ -66,7 +62,8 @@
             String json = readFile(file, filename);
             if (json != null) {
                 cacheVC(json, filename);
-                storeVC(filename, json);
+                vcService.storeVC(filename, VirtualCorpusType.SYSTEM, json, null,
+                        null, null, true, "system");
             }
         }
     }
@@ -113,17 +110,4 @@
                 + KrillCollection.cache.calculateInMemorySize());
     }
 
-    private void storeVC (String name, String koralQuery)
-            throws KustvaktException {
-        if (!VirtualCorpusService.wordPattern.matcher(name).matches()) {
-            throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
-                    "Virtual corpus name must only contains letters, numbers, "
-                            + "underscores, hypens and spaces",
-                    name);
-        }
-        CorpusAccess requiredAccess =
-                vcService.determineRequiredAccess(koralQuery);
-        vcDao.createVirtualCorpus(name, VirtualCorpusType.SYSTEM,
-                requiredAccess, koralQuery, null, null, null, true, "system");
-    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
index 8898685..bd24b21 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
@@ -1,6 +1,8 @@
 package de.ids_mannheim.korap.dao;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
@@ -78,8 +80,7 @@
         return (Role) q.getSingleResult();
     }
 
-    @SuppressWarnings("unchecked")
-    public List<Role> retrieveRoleByGroupMemberId (int userId) {
+    public Set<Role> retrieveRoleByGroupMemberId (int userId) {
         CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
         CriteriaQuery<Role> query = criteriaBuilder.createQuery(Role.class);
 
@@ -91,7 +92,9 @@
         query.where(criteriaBuilder.equal(memberRole.get(UserGroupMember_.id),
                 userId));
         Query q = entityManager.createQuery(query);
-        return q.getResultList();
+        @SuppressWarnings("unchecked")
+        List<Role> resultList = q.getResultList();
+        return new HashSet<Role>(resultList);
     }
 
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/Role.java b/full/src/main/java/de/ids_mannheim/korap/entity/Role.java
index 1474316..0eaf767 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/Role.java
+++ b/full/src/main/java/de/ids_mannheim/korap/entity/Role.java
@@ -16,9 +16,10 @@
 import lombok.Getter;
 import lombok.Setter;
 
-/** Describes user roles for example in managing a group or 
- *  virtual corpora of a group.
- *  
+/**
+ * Describes user roles for example in managing a group or
+ * virtual corpora of a group.
+ * 
  * @author margaretha
  * @see Privilege
  */
@@ -26,7 +27,7 @@
 @Getter
 @Entity
 @Table(name = "role")
-public class Role implements Comparable<Role>{
+public class Role implements Comparable<Role> {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private int id;
@@ -46,12 +47,29 @@
 
     @Override
     public int compareTo (Role o) {
-        if (this.getId() > o.getId()){
+        if (this.getId() > o.getId()) {
             return 1;
         }
-        else if (this.getId() < o.getId()){
+        else if (this.getId() < o.getId()) {
             return -1;
         }
         return 0;
     }
+
+    @Override
+    public boolean equals (Object obj) {
+        Role r = (Role) obj;
+        if (this.id == r.getId() && this.name.equals(r.getName())) {
+            return true;
+        }
+        return false;
+    }
+    
+    @Override
+    public int hashCode () {
+        int hash = 7;
+        hash = 31 * hash + (int) id;
+        hash = 31 * hash + (name == null ? 0 : name.hashCode());
+        return hash;
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Scope.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Scope.java
index c44cc53..0dc20d5 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Scope.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Scope.java
@@ -24,7 +24,8 @@
     DELETE_USER_GROUP_MEMBER, 
     ADD_USER_GROUP_MEMBER, 
     
-    ADD_USER_GROUP_MEMBER_ROLE, 
+    EDIT_USER_GROUP_MEMBER_ROLE,
+    ADD_USER_GROUP_MEMBER_ROLE,
     DELETE_USER_GROUP_MEMBER_ROLE, 
            
     CREATE_VC, 
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 fdcc4ab..9133169 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
@@ -32,7 +32,9 @@
 import de.ids_mannheim.korap.web.controller.UserGroupController;
 import de.ids_mannheim.korap.web.input.UserGroupJson;
 
-/** UserGroupService defines the logic behind user group web controller.
+/**
+ * UserGroupService defines the logic behind user group web
+ * controller.
  * 
  * @see UserGroupController
  * 
@@ -42,8 +44,7 @@
 @Service
 public class UserGroupService {
 
-    private static Logger jlog =
-            LogManager.getLogger(UserGroupService.class);
+    private static Logger jlog = LogManager.getLogger(UserGroupService.class);
     @Autowired
     private UserGroupDao userGroupDao;
     @Autowired
@@ -61,10 +62,12 @@
 
     private static Set<Role> memberRoles;
 
-    /** Only users with {@link PredefinedRole#USER_GROUP_ADMIN} 
+    /**
+     * Only users with {@link PredefinedRole#USER_GROUP_ADMIN}
      * are allowed to see the members of the group.
      * 
-     * @param username username
+     * @param username
+     *            username
      * @return a list of usergroups
      * @throws KustvaktException
      * 
@@ -166,24 +169,27 @@
         }
     }
 
-    /** Group owner is automatically added when creating a group. 
-     *  Do not include owners in group members. 
-     *  
-     *  {@link PredefinedRole#USER_GROUP_MEMBER} and 
-     *  {@link PredefinedRole#VC_ACCESS_MEMBER} roles are 
-     *  automatically assigned to each group member. 
-     *  
-     *  {@link PredefinedRole#USER_GROUP_MEMBER} restrict users 
-     *  to see other group members and allow users to remove 
-     *  themselves from the groups.
-     *   
-     *  {@link PredefinedRole#VC_ACCESS_MEMBER} allow user to 
-     *  read group VC.
+    /**
+     * Group owner is automatically added when creating a group.
+     * Do not include owners in group members.
+     * 
+     * {@link PredefinedRole#USER_GROUP_MEMBER} and
+     * {@link PredefinedRole#VC_ACCESS_MEMBER} roles are
+     * automatically assigned to each group member.
+     * 
+     * {@link PredefinedRole#USER_GROUP_MEMBER} restrict users
+     * to see other group members and allow users to remove
+     * themselves from the groups.
+     * 
+     * {@link PredefinedRole#VC_ACCESS_MEMBER} allow user to
+     * read group VC.
      * 
      * @see /full/src/main/resources/db/predefined/V3.2__insert_predefined_roles.sql
      * 
-     * @param groupJson UserGroupJson object from json
-     * @param createdBy the user creating the group
+     * @param groupJson
+     *            UserGroupJson object from json
+     * @param createdBy
+     *            the user creating the group
      * @throws KustvaktException
      * 
      * 
@@ -240,19 +246,26 @@
                 config.isSoftDeleteAutoGroup());
     }
 
-    /** Adds a user to the specified usergroup. If the username with 
-     *  {@link GroupMemberStatus} DELETED exists as a member of the group, 
-     *  the entry will be deleted first, and a new entry will be added.
-     *  
-     *  If a username with other statuses exists, a KustvaktException will 
-     *  be thrown.    
+    /**
+     * Adds a user to the specified usergroup. If the username with
+     * {@link GroupMemberStatus} DELETED exists as a member of the
+     * group,
+     * the entry will be deleted first, and a new entry will be added.
+     * 
+     * If a username with other statuses exists, a KustvaktException
+     * will
+     * be thrown.
      * 
      * @see GroupMemberStatus
      * 
-     * @param username a username
-     * @param userGroup a user group
-     * @param createdBy the user (VCA/system) adding the user the user-group 
-     * @param status the status of the membership
+     * @param username
+     *            a username
+     * @param userGroup
+     *            a user group
+     * @param createdBy
+     *            the user (VCA/system) adding the user the user-group
+     * @param status
+     *            the status of the membership
      * @throws KustvaktException
      */
     public void inviteGroupMember (String username, UserGroup userGroup,
@@ -357,11 +370,15 @@
         return false;
     }
 
-    /** Updates the {@link GroupMemberStatus} of a pending member 
-     * to {@link GroupMemberStatus#ACTIVE} and add default member roles.
+    /**
+     * Updates the {@link GroupMemberStatus} of a pending member
+     * to {@link GroupMemberStatus#ACTIVE} and add default member
+     * roles.
      * 
-     * @param groupId groupId
-     * @param username the username of the group member
+     * @param groupId
+     *            groupId
+     * @param username
+     *            the username of the group member
      * @throws KustvaktException
      */
     public void acceptInvitation (int groupId, String username)
@@ -452,14 +469,19 @@
         }
     }
 
-    /** Updates the {@link GroupMemberStatus} of a member to 
+    /**
+     * Updates the {@link GroupMemberStatus} of a member to
      * {@link GroupMemberStatus#DELETED}
      * 
-     * @param userId user to be deleted
-     * @param groupId user-group id
-     * @param deletedBy user that issue the delete 
-     * @param isSoftDelete true if database entry is to be deleted 
-     * permanently, false otherwise
+     * @param userId
+     *            user to be deleted
+     * @param groupId
+     *            user-group id
+     * @param deletedBy
+     *            user that issue the delete
+     * @param isSoftDelete
+     *            true if database entry is to be deleted
+     *            permanently, false otherwise
      * @throws KustvaktException
      */
     private void doDeleteMember (String username, int groupId, String deletedBy,
@@ -495,8 +517,9 @@
 
     }
 
-    public void addMemberRoles (String username, int groupId,
-            String memberUsername, List<Integer> roleIds) throws KustvaktException {
+    public void editMemberRoles (String username, int groupId,
+            String memberUsername, List<Integer> roleIds)
+            throws KustvaktException {
 
         ParameterChecker.checkIntegerValue(groupId, "groupId");
         ParameterChecker.checkStringValue(username, "username");
@@ -504,9 +527,49 @@
 
         UserGroup userGroup = userGroupDao.retrieveGroupById(groupId, true);
         UserGroupStatus groupStatus = userGroup.getStatus();
-        if (groupStatus == UserGroupStatus.DELETED){
+        if (groupStatus == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
-                    "Usergroup has been deleted."); 
+                    "Usergroup has been deleted.");
+        }
+        else if (isUserGroupAdmin(username, userGroup)
+                || adminDao.isAdmin(username)) {
+
+            UserGroupMember member =
+                    groupMemberDao.retrieveMemberById(memberUsername, groupId);
+
+            if (!member.getStatus().equals(GroupMemberStatus.ACTIVE)) {
+                throw new KustvaktException(StatusCodes.GROUP_MEMBER_INACTIVE,
+                        memberUsername + " has status " + member.getStatus(),
+                        memberUsername, member.getStatus().name());
+            }
+
+            Set<Role> roles = new HashSet<>();
+            for (int i = 0; i < roleIds.size(); i++) {
+                roles.add(roleDao.retrieveRoleById(roleIds.get(i)));
+            }
+            member.setRoles(roles);
+            groupMemberDao.updateMember(member);
+
+        }
+        else {
+            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                    "Unauthorized operation for user: " + username, username);
+        }
+    }
+
+    public void addMemberRoles (String username, int groupId,
+            String memberUsername, List<Integer> roleIds)
+            throws KustvaktException {
+
+        ParameterChecker.checkIntegerValue(groupId, "groupId");
+        ParameterChecker.checkStringValue(username, "username");
+        ParameterChecker.checkStringValue(memberUsername, "memberUsername");
+
+        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId, true);
+        UserGroupStatus groupStatus = userGroup.getStatus();
+        if (groupStatus == UserGroupStatus.DELETED) {
+            throw new KustvaktException(StatusCodes.GROUP_DELETED,
+                    "Usergroup has been deleted.");
         }
         else if (isUserGroupAdmin(username, userGroup)
                 || adminDao.isAdmin(username)) {
@@ -535,7 +598,8 @@
     }
 
     public void deleteMemberRoles (String username, int groupId,
-            String memberUsername, List<Integer> roleIds) throws KustvaktException {
+            String memberUsername, List<Integer> roleIds)
+            throws KustvaktException {
 
         ParameterChecker.checkIntegerValue(groupId, "groupId");
         ParameterChecker.checkStringValue(username, "username");
@@ -551,8 +615,8 @@
 
             Set<Role> roles = member.getRoles();
             Iterator<Role> i = roles.iterator();
-            while (i.hasNext()){
-                if (roleIds.contains(i.next().getId())){
+            while (i.hasNext()) {
+                if (roleIds.contains(i.next().getId())) {
                     i.remove();
                 }
             }
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 33af77b..5d2ce4b 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
@@ -1,5 +1,6 @@
 package de.ids_mannheim.korap.service;
 
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -212,7 +213,7 @@
         // check if hidden access exists
         if (access == null) {
             VirtualCorpus vc = vcDao.retrieveVCById(vcId);
-            // create and assign a hidden group
+            // create and assign a new hidden group
             int groupId = userGroupService.createAutoHiddenGroup(vcId);
             UserGroup autoHidden =
                     userGroupService.retrieveUserGroupById(groupId);
@@ -220,18 +221,28 @@
                     VirtualCorpusAccessStatus.HIDDEN);
         }
         else {
+            // should not happened
             jlog.error("Cannot publish VC with id: " + vcId
-                    + ". There have been hidden accesses for the VC already.");
+                    + ". Hidden access exists! Access id: " + access.getId());
         }
     }
 
     public int storeVC (VirtualCorpusJson vc, String username)
             throws KustvaktException {
-        ParameterChecker.checkStringValue(vc.getName(), "name");
-        ParameterChecker.checkObjectValue(vc.getType(), "type");
         ParameterChecker.checkStringValue(vc.getCorpusQuery(), "corpusQuery");
+        String koralQuery = serializeCorpusQuery(vc.getCorpusQuery());
 
-        String name = vc.getName();
+        return storeVC(vc.getName(), vc.getType(), koralQuery,
+                vc.getDefinition(), vc.getDescription(), vc.getStatus(),
+                vc.isCached(), username);
+    }
+
+    public int storeVC (String name, VirtualCorpusType type, String koralQuery,
+            String definition, String description, String status,
+            boolean isCached, String username) throws KustvaktException {
+        ParameterChecker.checkStringValue(name, "name");
+        ParameterChecker.checkObjectValue(type, "type");
+
         if (!wordPattern.matcher(name).matches()) {
             throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
                     "Virtual corpus name must only contains letters, numbers, "
@@ -239,20 +250,36 @@
                     name);
         }
 
-        if (vc.getType().equals(VirtualCorpusType.SYSTEM)
+        if (type.equals(VirtualCorpusType.SYSTEM) 
+                && !username.equals("system")
                 && !adminDao.isAdmin(username)) {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
 
-        String koralQuery = serializeCorpusQuery(vc.getCorpusQuery());
         CorpusAccess requiredAccess = determineRequiredAccess(koralQuery);
 
-        int vcId = vcDao.createVirtualCorpus(vc.getName(), vc.getType(),
-                requiredAccess, koralQuery, vc.getDefinition(),
-                vc.getDescription(), vc.getStatus(), vc.isCached(), username);
+        int vcId = 0;
+        try {
+            vcId = vcDao.createVirtualCorpus(name, type, requiredAccess,
+                    koralQuery, definition, description, status, isCached,
+                    username);
 
-        if (vc.getType().equals(VirtualCorpusType.PUBLISHED)) {
+        }
+        catch (Exception e) {
+            Throwable cause = e;
+            Throwable lastCause = null;
+            while ((cause = cause.getCause()) != null
+                    && !cause.equals(lastCause)) {
+                if (cause instanceof SQLException) {
+                    break;
+                }
+                lastCause = cause;
+            }
+            throw new KustvaktException(StatusCodes.DB_INSERT_FAILED,
+                    cause.getMessage());
+        }
+        if (type.equals(VirtualCorpusType.PUBLISHED)) {
             publishVC(vcId);
         }
         // EM: should this return anything?
@@ -326,8 +353,23 @@
                     "Unauthorized operation for user: " + username, username);
         }
         else {
-            accessDao.createAccessToVC(vc, userGroup, username,
-                    VirtualCorpusAccessStatus.ACTIVE);
+            try {
+                accessDao.createAccessToVC(vc, userGroup, username,
+                        VirtualCorpusAccessStatus.ACTIVE);
+            }
+            catch (Exception e) {
+                Throwable cause = e;
+                Throwable lastCause = null;
+                while ((cause = cause.getCause()) != null
+                        && !cause.equals(lastCause)) {
+                    if (cause instanceof SQLException) {
+                        break;
+                    }
+                    lastCause = cause;
+                }
+                throw new KustvaktException(StatusCodes.DB_INSERT_FAILED,
+                        cause.getMessage());
+            }
             vcDao.editVirtualCorpus(vc, null, VirtualCorpusType.PUBLISHED, null,
                     null, null, null, null);
         }
@@ -417,8 +459,8 @@
 
     }
 
-    public VirtualCorpus searchVCByName (String username, String vcName, String createdBy)
-            throws KustvaktException {
+    public VirtualCorpus searchVCByName (String username, String vcName,
+            String createdBy) throws KustvaktException {
         VirtualCorpus vc = vcDao.retrieveVCByName(vcName, createdBy);
         checkVCAccess(vc, username);
         return vc;
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 3d9aeb9..6c9427d 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
@@ -24,12 +24,13 @@
 import de.ids_mannheim.korap.constant.UserGroupStatus;
 import de.ids_mannheim.korap.dto.UserGroupDto;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Scope;
 import de.ids_mannheim.korap.oauth2.service.OAuth2ScopeService;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.service.UserGroupService;
-import de.ids_mannheim.korap.web.KustvaktResponseHandler;
 import de.ids_mannheim.korap.web.APIVersionFilter;
+import de.ids_mannheim.korap.web.KustvaktResponseHandler;
 import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
 import de.ids_mannheim.korap.web.filter.BlockingFilter;
 import de.ids_mannheim.korap.web.filter.PiwikFilter;
@@ -267,6 +268,35 @@
         }
     }
 
+    @POST
+    @Path("member/role/edit")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response editMemberRoles (@Context SecurityContext securityContext,
+            @FormParam("groupId") int groupId,
+            @FormParam("memberUsername") String memberUsername,
+            @FormParam("roleIds") List<Integer> roleIds,
+            @PathParam("version") String version) {
+        double v = Double.valueOf(version.substring(1, version.length()));
+        if (v < 1.1) {
+            throw kustvaktResponseHandler.throwit(new KustvaktException(
+                    StatusCodes.UNSUPPORTED_API_VERSION,
+                    "Method is not supported in version " + version, version));
+        }
+
+        TokenContext context =
+                (TokenContext) securityContext.getUserPrincipal();
+        try {
+            scopeService.verifyScope(context,
+                    OAuth2Scope.EDIT_USER_GROUP_MEMBER_ROLE);
+            service.editMemberRoles(context.getUsername(), groupId,
+                    memberUsername, roleIds);
+            return Response.ok().build();
+        }
+        catch (KustvaktException e) {
+            throw kustvaktResponseHandler.throwit(e);
+        }
+    }
+
     /**
      * Adds roles of an active member of a user-group. Only user-group
      * admins
diff --git a/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
index 55f2163..eb9d1df 100644
--- a/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
@@ -64,6 +64,7 @@
   corpus_query TEXT NOT NULL,
   definition varchar(255) DEFAULT NULL,
   is_cached BOOLEAN DEFAULT 0,
+  UNIQUE INDEX unique_index (name,created_by),
   INDEX owner_index (created_by),
   INDEX type_index (type)
 );
diff --git a/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
index 56fc105..0547591 100644
--- a/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
@@ -78,6 +78,8 @@
 
 CREATE INDEX virtual_corpus_owner_index ON virtual_corpus(created_by);
 CREATE INDEX virtual_corpus_type_index ON virtual_corpus(type);
+CREATE UNIQUE INDEX  virtual_corpus_unique_name 
+	ON virtual_corpus(name,created_by);
 
 CREATE TABLE IF NOT EXISTS virtual_corpus_access (
   id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -97,6 +99,6 @@
 
 CREATE INDEX virtual_corpus_status_index 
 	ON virtual_corpus_access(status);
-CREATE INDEX virtual_corpus_access_unique_index 
+CREATE UNIQUE INDEX virtual_corpus_access_unique_index 
 	ON virtual_corpus_access(virtual_corpus_id,user_group_id);
 
diff --git a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
index db238ab..e0c06d6 100644
--- a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
@@ -20,8 +20,6 @@
 	   REFERENCES oauth2_client_url(url_hashcode)
 );
 
-CREATE UNIQUE INDEX client_id_index on oauth2_client(id);
-
 CREATE TABLE IF NOT EXISTS oauth2_access_scope (
 	id VARCHAR(255) PRIMARY KEY NOT NULL
 );
diff --git a/full/src/main/resources/kustvakt.conf b/full/src/main/resources/kustvakt.conf
index 3932b82..c5a4958 100644
--- a/full/src/main/resources/kustvakt.conf
+++ b/full/src/main/resources/kustvakt.conf
@@ -12,7 +12,7 @@
 ldap.config = file-path-to-ldap-config
 
 # Kustvakt
-current.api.version = v1.0
+current.api.version = v1.1
 # multiple versions separated by space
 supported.api.version = v1.0