text: """
  Encapsulates elasticsearch indices  and provides authorisation through user roles
  
  Authorisation rules:
  - There are 5 increasing levels of authorisation: None, metareader, reader, writer, admin
  - Users can have a global role and a role on any index. Every index can also have a guest role
  - Global roles:
    - Writers can create new projects and users (with at most their own global role)
    - Admins can delete projects and assign themselves a role on any index role
  - Index roles:
    - None means an index cannot be viewed or accessed in any way (the index is invisible to the user)
    - Metareader means the user can read all properties, do queries, etc., but cannot read the 'text' attribute
      (this is mostly intended to provide access to metadata fields of copyrighted material)
    - Reader can read all properties, do queries, etc., but cannot make changes
    - Writers can add/delete documents, add/delete users (up to their own level), and make other changes (but not delete)
    - Admins can do whatever they want, including deleting the index
  - If a user does not have an explicit role on an index, the guest role (if any) is used
  - An unauthorized user can still get guest roles, so it can see any indices with a guest role
  
  Note that these rules are not enforced in this module, they should be enforced by the API!
  """
  import logging
  
  from peewee import Model, CharField, IntegerField, ForeignKeyField
  
  from livingHub import elastic
  from livingHub.auth import Role, User
  from livingHub.db import db
  
  
  class Index(Model):
      name = CharField(unique=True)
      guest_role = IntegerField(null=True)
  
      class Meta:
          database = db
  
      def delete_index(self, delete_from_elastic=True):
          """
          Delete this index
          :param delete_from_elastic: if True (default), also delete the underlying elastic table
          """
          self.delete_instance()
          if delete_from_elastic:
              elastic._delete_index(self.name)
          indices = [i.name for i in self.select()]
  
      def has_role(self, user: User, role: Role) -> bool:
          """
          Checks whether the given User has at least the required Role
          """
          ir = IndexRole.get_or_none(IndexRole.user == user, IndexRole.index == self)
          actual_role = self.guest_role if ir is None else ir.role
          if not actual_role:
              return False
          else:
              return actual_role and actual_role >= role
  
      def set_role(self, user: User, role: Role) -> None:
          """
          Sets the role for the given new or existing user; set role to None to remove user from this index.
          This will create/update/delete the role as needed
          """
          ir = IndexRole.get_or_none(IndexRole.user == user, IndexRole.index == self)
          if ir:
              if role is None:
                  ir.delete_instance()
              elif role != ir.role:
                  ir.role = role
                  ir.save()
          elif role is not None:
              IndexRole.create(user=user, index=self, role=role)
  
  
  class IndexRole(Model):
      user = ForeignKeyField(User, on_delete="CASCADE")
      index = ForeignKeyField(Index, on_delete="CASCADE")
      role = IntegerField()
  
      class Meta:
          database = db
          indexes = (
              (('user', 'index'), True),  # unique constraint user & index
          )
  
  
  def create_index(name: str, guest_role: Role = None, create_in_elastic=True, admin: User = None) -> Index:
      """
      Create a new index in both elastic and amcat4
      :param name: Name of the new index (in elastic and amcat4)
      :param guest_role: Guest role for this index, i.e. a user's role if no explicit role is assigned.
      :param admin: if given, add this User as admin
      :param create_in_elastic: if True (default), also create a new elastic index
      """
      if Index.select().where(Index.name == name).exists():
          raise ValueError("Index {name} is already registered".format(**locals()))
      if create_in_elastic:
          if elastic.index_exists(name):
              raise ValueError("Index {name} already exists in elastic".format(**locals()))
          elastic._create_index(name)
      elif not elastic.index_exists(name):
          raise ValueError("Index {name} does not exist in elastic".format(**locals()))
  
      index = Index.create(name=name, guest_role=guest_role)
      if admin:
          IndexRole.create(user=admin, index=index, role=Role.ADMIN)
  
      indices = [i.name for i in Index.select()]
  
      return index
  