diff --git a/.gitignore b/.gitignore
index a63696d55a0014c022e5b159a15164cd64d86626..2f720928eb837792f3e68b41d3db2479f2f02152 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
 # contains data for local tests
 app/
 site/
+.coverage
\ No newline at end of file
diff --git a/userdb-cli.py b/userdb-cli.py
old mode 100644
new mode 100755
index 5fa2f3cda3ac8a36f7b1d7f184fca613c4bab4c6..b17226fff71cc4372f7a7599f9c86d4516768e90
--- a/userdb-cli.py
+++ b/userdb-cli.py
@@ -1,54 +1,142 @@
-from apiserver import settings
-
-from apiserver.security import JsonDBInterface, get_password_hash, UserInDB
-
-import getopt, sys, getpass
-
-def main(argv):
-    userdb = JsonDBInterface(settings)
-    noarg = True
-    try:
-        opts, args = getopt.getopt(argv,"hla:d:g:",["list", "add=", "delete=", "get=", "help"])
-    except getopt.GetoptError:
-      print("Run \"userdb-cli.py -h\" for help.")
-      sys.exit(2)
-
-    for opt, arg in opts:
-        noarg = False
-        if opt == '-h':
-            # print help info
-            print("The following options are supported:")
-            print("\t-l | --list \t\t\t\tLists all users in the userdb-file")
-            print("\t-a <username> | --add <username>\tAdd a user with the given username. Email and password will be queried via the terminal.")
-            print("\t-g <username> | --get <username>\tPrint all information about the given username (i.e. username, email and password hash).")
-            print("\t-d <username> | --delete <username>\tDelete the given user from the file. This also deletes email and the stored password hash. This can not be undone. A Confirmation via the Terminal is required.")
-            print("Multiple operations can be done with one call, but they can be executed in any order. Email/password entry and deletion confirmation will have a label to show which username is currently used.")
-        elif opt in ("-l", "--list"):
-            for user in userdb.list():
-                print(user)
-        elif opt in ("-a", "--add"):
-            user = UserInDB(username=arg)
-            user.email = input("Enter email for user \"" + arg + "\": ")
-            password = getpass.getpass()
-            user.hashed_password = get_password_hash(password)
-            userdb.add(user)
-        elif opt in ("-g", "--get"):
-            user = userdb.get(arg)
-            print(user)
-        elif opt in ("-d", "--delete"):
-            confirmation = input("Are you sure you want to delete user \"" + arg +"\"?(Y/default:N)")
-            if confirmation == "Y" or confirmation == "y":
-                userdb.delete(arg)
-                print("User deleted.")
-            else:
-                print("User not deleted")
+#! /usr/bin/python3
+import os, json, argparse, abc
+
+from pydantic import BaseModel
+from typing import List
+from passlib.context import CryptContext
+
+
+
+class User(BaseModel):
+    username: str
+    email: str = None
+
+
+class UserInDB(User):
+    hashed_password: str = None
+
+
+class AbstractDBInterface(metaclass=abc.ABCMeta):
+    @abc.abstractclassmethod
+    def list(self) -> List:
+        raise NotImplementedError()
+
+    @abc.abstractclassmethod
+    def get(self, username: str):
+        raise NotImplementedError()
+
+    @abc.abstractclassmethod
+    def add(self, user: UserInDB):
+        raise NotImplementedError()
+
+    @abc.abstractclassmethod
+    def delete(self, username: str):
+        raise NotImplementedError()
+
+
+class JsonDBInterface(AbstractDBInterface):
+
+    def __init__(self, filepath):
+        self.file_path = filepath
+        if not (os.path.exists(self.file_path) and os.path.isfile(self.file_path)):
+            # create empty json
+            self.__save_all({})
         else:
-            print("Wrong argument: " + opt)
-            print("Run \"userdb-cli.py -h\" for help.")
-    if noarg:
-        print("No arguments.")
-        print("Run \"userdb-cli.py -h\" for help.")
+            # if it exists, check if it is valid
+            _ = self.__read_all()
+
+    def __read_all(self):
+        with open(self.file_path, 'r') as f:
+            return json.load(f)
+
+    def __save_all(self, data):
+        with open(self.file_path, 'w') as f:
+            json.dump(data, f)
+
+    def list(self):
+        data = self.__read_all()
+        return list(data.keys())
+
+    def get(self, username: str):
+        data = self.__read_all()
+        if username not in data:
+            return None
+
+        return UserInDB(**data[username])
+
+    def add(self, user: UserInDB):
+        data = self.__read_all()
+        if user.username in data:
+            raise Exception(f"User {user.username} already exists!")
+
+        data[user.username] = user.dict()
+        self.__save_all(data=data)
+
+    def delete(self, username: str):
+        data = self.__read_all()
+        # idempotent? or return?
+        _ = data.pop(username, None)
+
+        self.__save_all(data)
+
+
+__pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+def get_password_hash(password):
+    return __pwd_context.hash(password)
+
+def main(args):
+    userdb = JsonDBInterface(args.userdb_path)
+
+    if 'hash' in args.operation:
+        if not args.password:
+            raise ValueError("Password is not set!")
+        print(get_password_hash(args.password))
+    elif 'ls' in args.operation:
+        for user in userdb.list():
+            print(user)
+    elif 'add' in args.operation:
+        if not args.username:
+            raise ValueError("Username is not set!")
+        if not args.mail:
+            raise ValueError("Mail is not set!")
+        hash = args.bcrypt_hash
+        if not args.bcrypt_hash:
+            if  not args.password:
+                raise ValueError("No Password or hash given!")
+            hash = get_password_hash(args.password)
+        
+        user = UserInDB(username=args.username, email=args.mail, hashed_password=hash)
+        userdb.add(user)
+        print("new User added:")
+        print(user)
+    elif 'show' in args.operation:
+        if not args.username:
+            raise ValueError("Username is not set!")
+        user = userdb.get(args.username)
+        print(user)
+    elif 'rm' in args.operation:
+        if not args.username:
+            raise ValueError("Username is not set!")
+        user = userdb.get(args.username)
+        print("Deleting the following user:")
+        print(user)
+        userdb.delete(args.username)
 
 
 if __name__ == "__main__":
-    main(sys.argv[1:])
\ No newline at end of file
+    parser = argparse.ArgumentParser("userdb-cli.py", description="CLI for a userdb.json for the datacatalog-apiserver.", formatter_class=argparse.RawTextHelpFormatter)
+    parser.add_argument("operation", type=str, nargs=1, help="\
+hash \tReturn a bcrypt hash for the given password. Requires -p. \n\
+ls \tLists all Users in the userdb. \n\
+add \tAdds a new user to the userdb. Requires -u, -m and either -p or -b. \n\
+show \tShows a single user from the userdb. Requires -u. \n\
+rm \tDeletes a single user from the userdb. Requires -u. \
+")
+    parser.add_argument("-u", "--username", help="The username that should be modified")
+    parser.add_argument("-m", "--mail", help="The email of a newly created user.")
+    parser.add_argument("-p", "--password", help="The password of a newly created user.")
+    parser.add_argument("-b", "--bcrypt-hash", help="The bcrypt password-hash of a newly created user.")
+    parser.add_argument("userdb_path", type=str, nargs='?', help="The path to the userdb to be modified or created.", default="./userdb.json")
+    args = parser.parse_args()
+    main(args)
\ No newline at end of file