# Module `database`

Expand source code

    import datetime
    import os
    import re
    import sqlite3 as sql
    import tempfile
    from src.logic.log import MyLogger
    #from icecream import ic
    from typing import List, Tuple, Dict, Any, Optional, Union
    from pathlib import Path
    from omegaconf import OmegaConf
    from src.backend.db import CREATE_TABLE_APPARAT, CREATE_TABLE_MESSAGES, CREATE_TABLE_MEDIA, CREATE_TABLE_APPKONTOS, CREATE_TABLE_FILES, CREATE_TABLE_PROF, CREATE_TABLE_USER, CREATE_TABLE_SUBJECTS
    from src.logic.constants import SEMAP_MEDIA_ACCOUNTS
    from src.logic.dataclass import ApparatData, BookData
    from src.errors import NoResultError, AppPresentError
    from src.utils import load_pickle, dump_pickle,create_blob
    config = OmegaConf.load("config.yaml")
    logger = MyLogger(__name__)

    class Database:
        """
        Initialize the database and create the tables if they do not exist.
        """
        def __init__(self, db_path: str = None):
            """
            Default constructor for the database class

            Args:
                db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
            """
            if db_path is None:
                self.db_path = config.database.path + config.database.name
                self.db_path = self.db_path.replace("~", str(Path.home()))
            else:
                self.db_path = db_path
            if self.get_db_contents() is None:
                logger.log_critical("Database does not exist, creating tables")
                self.create_tables()

        def get_db_contents(self)->Union[List[Tuple], None]:
            """
            Get the contents of the

            Returns:
                Union[List[Tuple], None]: _description_
            """
            try:
                with sql.connect(self.db_path) as conn:
                    cursor = conn.cursor()
                    cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
                    return cursor.fetchall()
            except sql.OperationalError:
                return None

        def connect(self)->sql.Connection:
            """
            Connect to the database

            Returns:
                sql.Connection: The active connection to the database
            """
            return sql.connect(self.db_path)

        def close_connection(self, conn: sql.Connection):
            """
            closes the connection to the database

            Args:
            ----
                - conn (sql.Connection): the connection to be closed
            """
            conn.close()

        def create_tables(self):
            """
            Create the tables in the database
            """
            conn = self.connect()
            cursor = conn.cursor()
            cursor.execute(CREATE_TABLE_APPARAT)
            cursor.execute(CREATE_TABLE_MESSAGES)
            cursor.execute(CREATE_TABLE_MEDIA)
            cursor.execute(CREATE_TABLE_APPKONTOS)
            cursor.execute(CREATE_TABLE_FILES)
            cursor.execute(CREATE_TABLE_PROF)
            cursor.execute(CREATE_TABLE_USER)
            cursor.execute(CREATE_TABLE_SUBJECTS)
            conn.commit()
            self.close_connection(conn)

        def insertInto(self, query:str, params:Tuple) -> None:
            """
            Insert sent data into the database

            Args:
                query (str): The query to be executed
                params (Tuple): the parameters to be inserted into the database
            """
            conn = self.connect()
            cursor = conn.cursor()
            logger.log_info(f"Inserting {params} into database with query {query}")
            cursor.execute(query, params)
            conn.commit()
            self.close_connection(conn)

        def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
            """
            Query the Database for the sent query.

            Args:
                query (str): The query to be executed
                args (Tuple, optional): The arguments for the query. Defaults to ().
                one (bool, optional): Return the first result only. Defaults to False.

            Returns:
                Union[Typle|List[Tuple]]: Returns the result of the query
            """
            conn = self.connect()
            cursor = conn.cursor()
            logger.log_info(f"Querying database with query {query}, args: {args}")
            cursor.execute(query, args)
            rv = cursor.fetchall()
            conn.commit()
            self.close_connection(conn)
            return (rv[0] if rv else None) if one else rv

        # Books
        def addBookToDatabase(self, bookdata:BookData,app_id:str, prof_id:str):
            """
            Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

            Args:
                bookdata (BookData): The metadata of the book to be added
                app_id (str): The apparat id where the book should be added to
                prof_id (str): The id of the professor where the book should be added to.
            """
            conn = self.connect()
            cursor = conn.cursor()
            t_query = (
                f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
            )
            # print(t_query)
            result = cursor.execute(t_query).fetchall()
            result = [load_pickle(i[0]) for i in result]
            if bookdata in result:
                print("Bookdata already in database")
                # check if the book was deleted in the apparat
                query = (
                    "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
                )
                params = (app_id, prof_id, dump_pickle(bookdata))
                result = cursor.execute(query, params).fetchone()
                if result[0] == 1:
                    print("Book was deleted, updating bookdata")
                    query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
                    params = (app_id, prof_id, dump_pickle(bookdata))
                    cursor.execute(query, params)
                    conn.commit()
                return

            query = (
                "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
            )
            converted = dump_pickle(bookdata)
            params = (converted, app_id, prof_id, 0)
            cursor.execute(query, params)
            conn.commit()
            self.close_connection(conn)
        def getBookIdBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->int:
            """
            Get a book id based on the signature of the book.

            Args:
                app_id (str): The apparat id the book should be associated with
                prof_id (str): The professor id the book should be associated with
                signature (str): The signature of the book

            Returns:
                int: The id of the book
            """
            result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
            books = [(load_pickle(i[0]),i[1]) for i in result]
            book = [i for i in books if i[0].signature == signature][0][1]
            return book
        def getBookBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->BookData:
            """
            Get the book based on the signature of the book.

            Args:
                app_id (str): The apparat id the book should be associated with
                prof_id (str): The professor id the book should be associated with
                signature (str): The signature of the book

            Returns:
                BookData: The total metadata of the book wrapped in a BookData object
            """
            result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
            books = [load_pickle(i[0]) for i in result]
            book = [i for i in books if i.signature == signature][0]
            return book
        def getLastBookId(self)->int:
            """
            Get the last book id in the database

            Returns:
                int: ID of the last book in the database
            """
            return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
        def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
            """
            Search a book in the database based on the sent data.

            Args:
                data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following:
                - signature: The signature of the book
                - title: The title of the book

            Returns:
                list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
            """
            rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
            #ic(rdata, len(rdata))
            mode = 0
            if len(data)== 1:
                if "signature" in data.keys():
                    mode = 1
                elif "title" in data.keys():
                    mode = 2
            elif len(data) == 2:
                mode = 3
            else:
                return None
            ret = []
            for book in rdata:
                bookdata = load_pickle(book[1])
                app_id = book[2]
                prof_id = book[3]
                if mode == 1:
                    if data["signature"] in bookdata.signature:
                        ret.append((bookdata,app_id,prof_id))
                elif mode == 2:
                    if data["title"] in bookdata.title:
                        ret.append((bookdata,app_id,prof_id))
                elif mode == 3:
                    if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
                        ret.append((bookdata,app_id,prof_id))
            #ic(ret)
            return ret
        def setAvailability(self, book_id:str, available:str):
            """
            Set the availability of a book in the database

            Args:
                book_id (str): The id of the book
                available (str): The availability of the book
            """
            self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
        def getBookId(self, bookdata:BookData, app_id, prof_id)->int:
            """
            Get the id of a book based on the metadata of the book

            Args:
                bookdata (BookData): The wrapped metadata of the book
                app_id (str): The apparat id the book should be associated with
                prof_id (str): The professor id the book should be associated with

            Returns:
                int: ID of the book
            """
            result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
            return result[0]
        def getBook(self,book_id:int)->BookData:
            """
            Get the book based on the id in the database

            Args:
                book_id (int): The id of the book

            Returns:
                BookData: The metadata of the book wrapped in a BookData object
            """
            return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
        def getBooks(self, app_id, prof_id, deleted=0)->list[dict[int, BookData, int]]:
            """
            Get the Books based on the apparat id and the professor id

            Args:
                app_id (str): The ID of the apparat
                prof_id (str): The ID of the professor
                deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.

            Returns:
                list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
            """
            qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
            ret_result = []
            for result_a in qdata:
                data = {"id": int, "bookdata": BookData, "available": int}
                data["id"] = result_a[0]
                data["bookdata"] = load_pickle(result_a[1])
                data["available"] = result_a[2]
                ret_result.append(data)
            return ret_result

        def updateBookdata(self, book_id, bookdata:BookData):
            """
            Update the bookdata in the database

            Args:
                book_id (str): The id of the book
                bookdata (BookData): The new metadata of the book
            """
            self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
        def deleteBook(self, book_id):
            """
            Delete a book from the database

            Args:
                book_id (str): ID of the book
            """
            self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))

        # File Interactions
        def getBlob(self, filename, app_id):
            """
            Get a blob from the database

            Args:
                filename (str): The name of the file
                app_id (str): ID of the apparat

            Returns:
                bytes: The file stored in
            """
            return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
        def insertFile(self, file: list[dict], app_id: int, prof_id):
            for f in file:
                filename = f["name"]
                path = f["path"]
                filetyp = f["type"]
                if path == "Database":
                    continue
                blob = create_blob(path)
                query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
                self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
        def recreateFile(self, filename, app_id,filetype):
            blob = self.getBlob(filename, app_id)
            tempdir = config.database.tempdir
            tempdir = tempdir.replace("~", str(Path.home()))
            tempdir_path = Path(tempdir)
            if not os.path.exists(tempdir_path):
                os.mkdir(tempdir_path)
            file = tempfile.NamedTemporaryFile(
                delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
            )
            file.write(blob)
            print("file created")
            return file.name
        def getFiles(self, app_id:int, prof_id:int)->list[tuple]:
            return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))

        def getSemersters(self):
            data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
            return [i[0] for i in data]

        def getSubjects(self):
            return self.query_db("SELECT * FROM subjects")

        # Messages
        def addMessage(self, message:dict,user, appnr):
            def __getUserId(user):
                return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
            user_id = __getUserId(user)
            self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],appnr))
        def getMessages(self, date:str):
            def __get_user_name(user_id):
                return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
            messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
            ret = [
                {
                    "message": i[2],
                    "user": __get_user_name(i[4]),
                    "appnr": i[5],
                    "id": i[0]
                }
                for i in messages
            ]
            return ret
        def deleteMessage(self, message_id):
            self.query_db("DELETE FROM messages WHERE id=?", (message_id,))

        # Prof data
        def getProfNameById(self, prof_id:int,add_title:bool=False):
            prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
            if add_title:
                return f"{self.getTitleById(prof_id)}{prof[0]}"
            else:
                return prof[0]
        def getTitleById(self, prof_id:int):
            title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
            return f"{title} " if title is not None else ""
        def getProfByName(self, prof_name:str):
            return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
        def getProfId(self, prof_name:str):
            """
            getProfId _summary_

            :param prof_name: _description_
            :type prof_name: str
            :return: _description_
            :rtype: _type_
            """
            data = self.getProfByName(prof_name.replace(",", ""))
            if data is None:
                return None
            else:
                return data[0]
        def getSpecificProfData(self, prof_id:int, fields:List[str]):
            """
            getSpecificProfData _summary_

            Args:
            ----
                - prof_id (int): _description_
                - fields (List[str]): _description_

            Returns:
            -------
                - _type_: _description_
            """
            query = "SELECT "
            for field in fields:
                query += f"{field},"
            query = query[:-1]
            query += " FROM prof WHERE id=?"
            return self.query_db(query, (prof_id,), one=True)[0]
        def getProfData(self, profname:str):

            data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
            return data
        def createProf(self, prof_details:dict):
            prof_title = prof_details["prof_title"]
            prof_fname = prof_details["profname"].split(",")[1]
            prof_fname = prof_fname.strip()
            prof_lname = prof_details["profname"].split(",")[0]
            prof_lname = prof_lname.strip()
            prof_fullname = prof_details["profname"].replace(",", "")
            prof_mail = prof_details["prof_mail"]
            prof_tel = prof_details["prof_tel"]
            params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
            query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
            self.insertInto(query=query, params=params)
        def getProfs(self):
            return self.query_db("SELECT * FROM prof")

        # Apparat
        def getAllAparats(self,deleted=0):
            return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
        def getApparatData(self, appnr, appname)->ApparatData:

            result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
            if result is None:
                raise NoResultError("No result found")
            apparat = ApparatData()
            apparat.appname = result[1]
            apparat.appnr = result[4]
            apparat.dauerapp = True if result[7] == 1 else False
            prof_data = self.getProfData(self.getProfNameById(result[2]))
            apparat.profname = self.getProfNameById(result[2])
            apparat.prof_mail = prof_data[0]
            apparat.prof_tel = prof_data[1]
            apparat.prof_title = prof_data[2]
            apparat.app_fach = result[3]
            apparat.erstellsemester = result[5]
            apparat.semester = result[8]
            apparat.deleted = result[9]
            apparat.apparat_adis_id = result[11]
            apparat.prof_adis_id = result[12]
            return apparat
        def getUnavailableApparatNumbers(self)->List[int]:
            """
            getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers

            Returns:
            -------
                - number(List[int]): a list of all currently used apparat numbers
            """
            numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")
            numbers = [i[0] for i in numbers]
            logger.log_info(f"Currently used apparat numbers: {numbers}")
            return numbers
        def setNewSemesterDate(self, appnr, newDate, dauerapp=False):
            date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
            if dauerapp:
                self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,appnr))
            else:
                self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,appnr))
        def getApparatId(self, apparat_name):
            data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
            if data is None:
                return None
            else:
                return data[0]
        def createApparat(self, apparat:ApparatData)->Optional[AppPresentError]|int:
            prof_id = self.getProfId(apparat.profname)
            app_id = self.getApparatId(apparat.appname)
            if app_id:
                return AppPresentError(app_id)

            self.createProf(apparat.get_prof_details())
            prof_id = self.getProfId(apparat.profname)
            #ic(prof_id)
            query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
            logger.log_info(query)
            self.query_db(query)
            return self.getApparatId(apparat.appname)
        def getApparatsByProf(self, prof_id:int)->list[tuple]:
            return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
        def getApparatsBySemester(self, semester:str)->dict:
            data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
            conn = self.connect()
            cursor = conn.cursor()
            c_tmp = []
            for i in data:
                c_tmp.append((i[0], self.getProfNameById(i[1])))
            query = (
                f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
            )
            result = cursor.execute(query).fetchall()
            d_tmp = []
            for i in result:
                d_tmp.append((i[0], self.getProfNameById(i[1])))
            # group the apparats by prof
            c_ret = {}
            for i in c_tmp:
                if i[1] not in c_ret.keys():
                    c_ret[i[1]] = [i[0]]
                else:
                    c_ret[i[1]].append(i[0])
            d_ret = {}
            for i in d_tmp:
                if i[1] not in d_ret.keys():
                    d_ret[i[1]] = [i[0]]
                else:
                    d_ret[i[1]].append(i[0])
            self.close_connection(conn)
            return {"created": c_ret, "deleted": d_ret}
        def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
            conn = self.connect()
            cursor = conn.cursor()
            semesters = self.getSemersters()
            created = []
            deleted = []
            for semester in semesters:
                query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
                result = cursor.execute(query).fetchone()
                created.append(result[0])
                query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
                result = cursor.execute(query).fetchone()
                deleted.append(result[0])
            # store data in a tuple
            ret = []
            e_tuple = ()
            for sem in semesters:
                e_tuple = (
                    sem,
                    created[semesters.index(sem)],
                    deleted[semesters.index(sem)],
                )
                ret.append(e_tuple)
            self.close_connection(conn)
            return ret
        def deleteApparat(self, appnr, semester):
            self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,appnr))
        def isEternal(self, id):
            return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
        def getApparatName(self, app_id, prof_id):
            return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
        def updateApparat(self, apparat_data:ApparatData):
            query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
            params = (
                apparat_data.appname,
                apparat_data.app_fach,
                apparat_data.dauerapp,
                self.getProfId(apparat_data.profname),
                apparat_data.appnr,
            )
            self.query_db(query, params)
        def checkApparatExists(self, apparat_name):
            return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
        def checkApparatExistsById(self, apparat_id):
            return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True) else False
        # Statistics
        def statistic_request(self, **kwargs: Any):
            def __query(query):
                conn = self.connect()
                cursor = conn.cursor()
                result = cursor.execute(query).fetchall()
                for result_a in result:
                    orig_value = result_a
                    prof_name = self.getProfNameById(result_a[2])
                    # replace the prof_id with the prof_name
                    result_a = list(result_a)
                    result_a[2] = prof_name
                    result_a = tuple(result_a)
                    result[result.index(orig_value)] = result_a
                self.close_connection(conn)
                return result
            if "deletable" in kwargs.keys():
                query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
                return __query(query)
            if "dauer" in kwargs.keys():
                kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
            query = "SELECT * FROM semesterapparat WHERE "
            for key, value in kwargs.items() if kwargs.items() is not None else {}:
                print(key, value)
                query += f"{key}='{value}' AND "
                print(query)
            # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
            if "deletesemester" in kwargs.keys():
                query = query.replace(
                    f"deletesemester='{kwargs['deletesemester']}' AND ", ""
                )
            if "endsemester" in kwargs.keys():
                if "erstellsemester" in kwargs.keys():
                    query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
                    query = query.replace(
                        f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
                    )
                else:
                    query = query.replace(
                        f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
                    )
                    print("replaced")
                query = query.replace(
                    "xyz",
                    f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
                )
            # remove all x="" parts from the query where x is a key in kwargs
            query = query[:-5]
            print(query)
            return __query(query)

        # Admin data
        def getUser(self):
            return self.query_db("SELECT * FROM user", one=True)
        def getUsers(self):
            return self.query_db("SELECT * FROM user")

        def login(self, user, hashed_password):
            salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
            if salt is None:
                return False
            hashed_password = salt + hashed_password
            password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
            if password == hashed_password:
                return True
            else:
                return False
        def changePassword(self, user, new_password):
            salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
            new_password = salt + new_password
            self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
        def getRole(self, user):
            return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
        def getRoles(self):
            return self.query_db("SELECT role FROM user")
        def checkUsername(self, user):
            data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
            return True if data is not None else False
        def createUser(self, user, password, role, salt):
            """Create a user based on passed data.

            Args:
            ----
                - username (str): Username to be used
                - password (str): the salted password
                - role (str): Role of the user
                - salt (str): a random salt for the user
            """
            self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
        def deleteUser(self, user):
            self.query_db("DELETE FROM user WHERE username=?", (user,))
        def updateUser(self, username, data:dict[str, str]):
            conn = self.connect()
            cursor = conn.cursor()
            query = "UPDATE user SET "
            for key,value in data.items():
                if key == "username":
                    continue
                query += f"{key}='{value}',"
            query = query[:-1]
            query += " WHERE username=?"
            params = (username,)
            cursor.execute(query, params)
            conn.commit()
            self.close_connection(conn)
        def getFacultyMember(self, name:str):
            return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
        def updateFacultyMember(self, data, oldlname, oldfname):
            placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
            query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
            data["oldlname"] = oldlname
            data["oldfname"] = oldfname
            self.query_db(query, data)
        def getFacultyMembers(self):
            return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")

## Classes

`class Database (db_path: str = None)`

  Initialize the database and create the tables if they do not exist.

  Default constructor for the database class

  ## Args

  `db_path` :`str`, optional
    Optional Path for testing / specific purposes. Defaults to None.
  Expand source code

      class Database:
          """
          Initialize the database and create the tables if they do not exist.
          """
          def __init__(self, db_path: str = None):
              """
              Default constructor for the database class

              Args:
                  db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
              """
              if db_path is None:
                  self.db_path = config.database.path + config.database.name
                  self.db_path = self.db_path.replace("~", str(Path.home()))
              else:
                  self.db_path = db_path
              if self.get_db_contents() is None:
                  logger.log_critical("Database does not exist, creating tables")
                  self.create_tables()

          def get_db_contents(self)->Union[List[Tuple], None]:
              """
              Get the contents of the

              Returns:
                  Union[List[Tuple], None]: _description_
              """
              try:
                  with sql.connect(self.db_path) as conn:
                      cursor = conn.cursor()
                      cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
                      return cursor.fetchall()
              except sql.OperationalError:
                  return None

          def connect(self)->sql.Connection:
              """
              Connect to the database

              Returns:
                  sql.Connection: The active connection to the database
              """
              return sql.connect(self.db_path)

          def close_connection(self, conn: sql.Connection):
              """
              closes the connection to the database

              Args:
              ----
                  - conn (sql.Connection): the connection to be closed
              """
              conn.close()

          def create_tables(self):
              """
              Create the tables in the database
              """
              conn = self.connect()
              cursor = conn.cursor()
              cursor.execute(CREATE_TABLE_APPARAT)
              cursor.execute(CREATE_TABLE_MESSAGES)
              cursor.execute(CREATE_TABLE_MEDIA)
              cursor.execute(CREATE_TABLE_APPKONTOS)
              cursor.execute(CREATE_TABLE_FILES)
              cursor.execute(CREATE_TABLE_PROF)
              cursor.execute(CREATE_TABLE_USER)
              cursor.execute(CREATE_TABLE_SUBJECTS)
              conn.commit()
              self.close_connection(conn)

          def insertInto(self, query:str, params:Tuple) -> None:
              """
              Insert sent data into the database

              Args:
                  query (str): The query to be executed
                  params (Tuple): the parameters to be inserted into the database
              """
              conn = self.connect()
              cursor = conn.cursor()
              logger.log_info(f"Inserting {params} into database with query {query}")
              cursor.execute(query, params)
              conn.commit()
              self.close_connection(conn)

          def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
              """
              Query the Database for the sent query.

              Args:
                  query (str): The query to be executed
                  args (Tuple, optional): The arguments for the query. Defaults to ().
                  one (bool, optional): Return the first result only. Defaults to False.

              Returns:
                  Union[Typle|List[Tuple]]: Returns the result of the query
              """
              conn = self.connect()
              cursor = conn.cursor()
              logger.log_info(f"Querying database with query {query}, args: {args}")
              cursor.execute(query, args)
              rv = cursor.fetchall()
              conn.commit()
              self.close_connection(conn)
              return (rv[0] if rv else None) if one else rv

          # Books
          def addBookToDatabase(self, bookdata:BookData,app_id:str, prof_id:str):
              """
              Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

              Args:
                  bookdata (BookData): The metadata of the book to be added
                  app_id (str): The apparat id where the book should be added to
                  prof_id (str): The id of the professor where the book should be added to.
              """
              conn = self.connect()
              cursor = conn.cursor()
              t_query = (
                  f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
              )
              # print(t_query)
              result = cursor.execute(t_query).fetchall()
              result = [load_pickle(i[0]) for i in result]
              if bookdata in result:
                  print("Bookdata already in database")
                  # check if the book was deleted in the apparat
                  query = (
                      "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
                  )
                  params = (app_id, prof_id, dump_pickle(bookdata))
                  result = cursor.execute(query, params).fetchone()
                  if result[0] == 1:
                      print("Book was deleted, updating bookdata")
                      query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
                      params = (app_id, prof_id, dump_pickle(bookdata))
                      cursor.execute(query, params)
                      conn.commit()
                  return

              query = (
                  "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
              )
              converted = dump_pickle(bookdata)
              params = (converted, app_id, prof_id, 0)
              cursor.execute(query, params)
              conn.commit()
              self.close_connection(conn)
          def getBookIdBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->int:
              """
              Get a book id based on the signature of the book.

              Args:
                  app_id (str): The apparat id the book should be associated with
                  prof_id (str): The professor id the book should be associated with
                  signature (str): The signature of the book

              Returns:
                  int: The id of the book
              """
              result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
              books = [(load_pickle(i[0]),i[1]) for i in result]
              book = [i for i in books if i[0].signature == signature][0][1]
              return book
          def getBookBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->BookData:
              """
              Get the book based on the signature of the book.

              Args:
                  app_id (str): The apparat id the book should be associated with
                  prof_id (str): The professor id the book should be associated with
                  signature (str): The signature of the book

              Returns:
                  BookData: The total metadata of the book wrapped in a BookData object
              """
              result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
              books = [load_pickle(i[0]) for i in result]
              book = [i for i in books if i.signature == signature][0]
              return book
          def getLastBookId(self)->int:
              """
              Get the last book id in the database

              Returns:
                  int: ID of the last book in the database
              """
              return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
          def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
              """
              Search a book in the database based on the sent data.

              Args:
                  data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following:
                  - signature: The signature of the book
                  - title: The title of the book

              Returns:
                  list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
              """
              rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
              #ic(rdata, len(rdata))
              mode = 0
              if len(data)== 1:
                  if "signature" in data.keys():
                      mode = 1
                  elif "title" in data.keys():
                      mode = 2
              elif len(data) == 2:
                  mode = 3
              else:
                  return None
              ret = []
              for book in rdata:
                  bookdata = load_pickle(book[1])
                  app_id = book[2]
                  prof_id = book[3]
                  if mode == 1:
                      if data["signature"] in bookdata.signature:
                          ret.append((bookdata,app_id,prof_id))
                  elif mode == 2:
                      if data["title"] in bookdata.title:
                          ret.append((bookdata,app_id,prof_id))
                  elif mode == 3:
                      if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
                          ret.append((bookdata,app_id,prof_id))
              #ic(ret)
              return ret
          def setAvailability(self, book_id:str, available:str):
              """
              Set the availability of a book in the database

              Args:
                  book_id (str): The id of the book
                  available (str): The availability of the book
              """
              self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
          def getBookId(self, bookdata:BookData, app_id, prof_id)->int:
              """
              Get the id of a book based on the metadata of the book

              Args:
                  bookdata (BookData): The wrapped metadata of the book
                  app_id (str): The apparat id the book should be associated with
                  prof_id (str): The professor id the book should be associated with

              Returns:
                  int: ID of the book
              """
              result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
              return result[0]
          def getBook(self,book_id:int)->BookData:
              """
              Get the book based on the id in the database

              Args:
                  book_id (int): The id of the book

              Returns:
                  BookData: The metadata of the book wrapped in a BookData object
              """
              return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
          def getBooks(self, app_id, prof_id, deleted=0)->list[dict[int, BookData, int]]:
              """
              Get the Books based on the apparat id and the professor id

              Args:
                  app_id (str): The ID of the apparat
                  prof_id (str): The ID of the professor
                  deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.

              Returns:
                  list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
              """
              qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
              ret_result = []
              for result_a in qdata:
                  data = {"id": int, "bookdata": BookData, "available": int}
                  data["id"] = result_a[0]
                  data["bookdata"] = load_pickle(result_a[1])
                  data["available"] = result_a[2]
                  ret_result.append(data)
              return ret_result

          def updateBookdata(self, book_id, bookdata:BookData):
              """
              Update the bookdata in the database

              Args:
                  book_id (str): The id of the book
                  bookdata (BookData): The new metadata of the book
              """
              self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
          def deleteBook(self, book_id):
              """
              Delete a book from the database

              Args:
                  book_id (str): ID of the book
              """
              self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))

          # File Interactions
          def getBlob(self, filename, app_id):
              """
              Get a blob from the database

              Args:
                  filename (str): The name of the file
                  app_id (str): ID of the apparat

              Returns:
                  bytes: The file stored in
              """
              return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
          def insertFile(self, file: list[dict], app_id: int, prof_id):
              for f in file:
                  filename = f["name"]
                  path = f["path"]
                  filetyp = f["type"]
                  if path == "Database":
                      continue
                  blob = create_blob(path)
                  query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
                  self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
          def recreateFile(self, filename, app_id,filetype):
              blob = self.getBlob(filename, app_id)
              tempdir = config.database.tempdir
              tempdir = tempdir.replace("~", str(Path.home()))
              tempdir_path = Path(tempdir)
              if not os.path.exists(tempdir_path):
                  os.mkdir(tempdir_path)
              file = tempfile.NamedTemporaryFile(
                  delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
              )
              file.write(blob)
              print("file created")
              return file.name
          def getFiles(self, app_id:int, prof_id:int)->list[tuple]:
              return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))

          def getSemersters(self):
              data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
              return [i[0] for i in data]

          def getSubjects(self):
              return self.query_db("SELECT * FROM subjects")

          # Messages
          def addMessage(self, message:dict,user, appnr):
              def __getUserId(user):
                  return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
              user_id = __getUserId(user)
              self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],appnr))
          def getMessages(self, date:str):
              def __get_user_name(user_id):
                  return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
              messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
              ret = [
                  {
                      "message": i[2],
                      "user": __get_user_name(i[4]),
                      "appnr": i[5],
                      "id": i[0]
                  }
                  for i in messages
              ]
              return ret
          def deleteMessage(self, message_id):
              self.query_db("DELETE FROM messages WHERE id=?", (message_id,))

          # Prof data
          def getProfNameById(self, prof_id:int,add_title:bool=False):
              prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
              if add_title:
                  return f"{self.getTitleById(prof_id)}{prof[0]}"
              else:
                  return prof[0]
          def getTitleById(self, prof_id:int):
              title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
              return f"{title} " if title is not None else ""
          def getProfByName(self, prof_name:str):
              return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
          def getProfId(self, prof_name:str):
              """
              getProfId _summary_

              :param prof_name: _description_
              :type prof_name: str
              :return: _description_
              :rtype: _type_
              """
              data = self.getProfByName(prof_name.replace(",", ""))
              if data is None:
                  return None
              else:
                  return data[0]
          def getSpecificProfData(self, prof_id:int, fields:List[str]):
              """
              getSpecificProfData _summary_

              Args:
              ----
                  - prof_id (int): _description_
                  - fields (List[str]): _description_

              Returns:
              -------
                  - _type_: _description_
              """
              query = "SELECT "
              for field in fields:
                  query += f"{field},"
              query = query[:-1]
              query += " FROM prof WHERE id=?"
              return self.query_db(query, (prof_id,), one=True)[0]
          def getProfData(self, profname:str):

              data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
              return data
          def createProf(self, prof_details:dict):
              prof_title = prof_details["prof_title"]
              prof_fname = prof_details["profname"].split(",")[1]
              prof_fname = prof_fname.strip()
              prof_lname = prof_details["profname"].split(",")[0]
              prof_lname = prof_lname.strip()
              prof_fullname = prof_details["profname"].replace(",", "")
              prof_mail = prof_details["prof_mail"]
              prof_tel = prof_details["prof_tel"]
              params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
              query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
              self.insertInto(query=query, params=params)
          def getProfs(self):
              return self.query_db("SELECT * FROM prof")

          # Apparat
          def getAllAparats(self,deleted=0):
              return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
          def getApparatData(self, appnr, appname)->ApparatData:

              result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
              if result is None:
                  raise NoResultError("No result found")
              apparat = ApparatData()
              apparat.appname = result[1]
              apparat.appnr = result[4]
              apparat.dauerapp = True if result[7] == 1 else False
              prof_data = self.getProfData(self.getProfNameById(result[2]))
              apparat.profname = self.getProfNameById(result[2])
              apparat.prof_mail = prof_data[0]
              apparat.prof_tel = prof_data[1]
              apparat.prof_title = prof_data[2]
              apparat.app_fach = result[3]
              apparat.erstellsemester = result[5]
              apparat.semester = result[8]
              apparat.deleted = result[9]
              apparat.apparat_adis_id = result[11]
              apparat.prof_adis_id = result[12]
              return apparat
          def getUnavailableApparatNumbers(self)->List[int]:
              """
              getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers

              Returns:
              -------
                  - number(List[int]): a list of all currently used apparat numbers
              """
              numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")
              numbers = [i[0] for i in numbers]
              logger.log_info(f"Currently used apparat numbers: {numbers}")
              return numbers
          def setNewSemesterDate(self, appnr, newDate, dauerapp=False):
              date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
              if dauerapp:
                  self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,appnr))
              else:
                  self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,appnr))
          def getApparatId(self, apparat_name):
              data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
              if data is None:
                  return None
              else:
                  return data[0]
          def createApparat(self, apparat:ApparatData)->Optional[AppPresentError]|int:
              prof_id = self.getProfId(apparat.profname)
              app_id = self.getApparatId(apparat.appname)
              if app_id:
                  return AppPresentError(app_id)

              self.createProf(apparat.get_prof_details())
              prof_id = self.getProfId(apparat.profname)
              #ic(prof_id)
              query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
              logger.log_info(query)
              self.query_db(query)
              return self.getApparatId(apparat.appname)
          def getApparatsByProf(self, prof_id:int)->list[tuple]:
              return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
          def getApparatsBySemester(self, semester:str)->dict:
              data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
              conn = self.connect()
              cursor = conn.cursor()
              c_tmp = []
              for i in data:
                  c_tmp.append((i[0], self.getProfNameById(i[1])))
              query = (
                  f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
              )
              result = cursor.execute(query).fetchall()
              d_tmp = []
              for i in result:
                  d_tmp.append((i[0], self.getProfNameById(i[1])))
              # group the apparats by prof
              c_ret = {}
              for i in c_tmp:
                  if i[1] not in c_ret.keys():
                      c_ret[i[1]] = [i[0]]
                  else:
                      c_ret[i[1]].append(i[0])
              d_ret = {}
              for i in d_tmp:
                  if i[1] not in d_ret.keys():
                      d_ret[i[1]] = [i[0]]
                  else:
                      d_ret[i[1]].append(i[0])
              self.close_connection(conn)
              return {"created": c_ret, "deleted": d_ret}
          def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
              conn = self.connect()
              cursor = conn.cursor()
              semesters = self.getSemersters()
              created = []
              deleted = []
              for semester in semesters:
                  query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
                  result = cursor.execute(query).fetchone()
                  created.append(result[0])
                  query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
                  result = cursor.execute(query).fetchone()
                  deleted.append(result[0])
              # store data in a tuple
              ret = []
              e_tuple = ()
              for sem in semesters:
                  e_tuple = (
                      sem,
                      created[semesters.index(sem)],
                      deleted[semesters.index(sem)],
                  )
                  ret.append(e_tuple)
              self.close_connection(conn)
              return ret
          def deleteApparat(self, appnr, semester):
              self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,appnr))
          def isEternal(self, id):
              return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
          def getApparatName(self, app_id, prof_id):
              return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
          def updateApparat(self, apparat_data:ApparatData):
              query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
              params = (
                  apparat_data.appname,
                  apparat_data.app_fach,
                  apparat_data.dauerapp,
                  self.getProfId(apparat_data.profname),
                  apparat_data.appnr,
              )
              self.query_db(query, params)
          def checkApparatExists(self, apparat_name):
              return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
          def checkApparatExistsById(self, apparat_id):
              return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True) else False
          # Statistics
          def statistic_request(self, **kwargs: Any):
              def __query(query):
                  conn = self.connect()
                  cursor = conn.cursor()
                  result = cursor.execute(query).fetchall()
                  for result_a in result:
                      orig_value = result_a
                      prof_name = self.getProfNameById(result_a[2])
                      # replace the prof_id with the prof_name
                      result_a = list(result_a)
                      result_a[2] = prof_name
                      result_a = tuple(result_a)
                      result[result.index(orig_value)] = result_a
                  self.close_connection(conn)
                  return result
              if "deletable" in kwargs.keys():
                  query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
                  return __query(query)
              if "dauer" in kwargs.keys():
                  kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
              query = "SELECT * FROM semesterapparat WHERE "
              for key, value in kwargs.items() if kwargs.items() is not None else {}:
                  print(key, value)
                  query += f"{key}='{value}' AND "
                  print(query)
              # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
              if "deletesemester" in kwargs.keys():
                  query = query.replace(
                      f"deletesemester='{kwargs['deletesemester']}' AND ", ""
                  )
              if "endsemester" in kwargs.keys():
                  if "erstellsemester" in kwargs.keys():
                      query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
                      query = query.replace(
                          f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
                      )
                  else:
                      query = query.replace(
                          f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
                      )
                      print("replaced")
                  query = query.replace(
                      "xyz",
                      f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
                  )
              # remove all x="" parts from the query where x is a key in kwargs
              query = query[:-5]
              print(query)
              return __query(query)

          # Admin data
          def getUser(self):
              return self.query_db("SELECT * FROM user", one=True)
          def getUsers(self):
              return self.query_db("SELECT * FROM user")

          def login(self, user, hashed_password):
              salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
              if salt is None:
                  return False
              hashed_password = salt + hashed_password
              password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
              if password == hashed_password:
                  return True
              else:
                  return False
          def changePassword(self, user, new_password):
              salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
              new_password = salt + new_password
              self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
          def getRole(self, user):
              return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
          def getRoles(self):
              return self.query_db("SELECT role FROM user")
          def checkUsername(self, user):
              data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
              return True if data is not None else False
          def createUser(self, user, password, role, salt):
              """Create a user based on passed data.

              Args:
              ----
                  - username (str): Username to be used
                  - password (str): the salted password
                  - role (str): Role of the user
                  - salt (str): a random salt for the user
              """
              self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
          def deleteUser(self, user):
              self.query_db("DELETE FROM user WHERE username=?", (user,))
          def updateUser(self, username, data:dict[str, str]):
              conn = self.connect()
              cursor = conn.cursor()
              query = "UPDATE user SET "
              for key,value in data.items():
                  if key == "username":
                      continue
                  query += f"{key}='{value}',"
              query = query[:-1]
              query += " WHERE username=?"
              params = (username,)
              cursor.execute(query, params)
              conn.commit()
              self.close_connection(conn)
          def getFacultyMember(self, name:str):
              return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
          def updateFacultyMember(self, data, oldlname, oldfname):
              placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
              query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
              data["oldlname"] = oldlname
              data["oldfname"] = oldfname
              self.query_db(query, data)
          def getFacultyMembers(self):
              return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")

  ### Methods

  `def addBookToDatabase(self, bookdata: src.logic.dataclass.BookData, app_id: str, prof_id: str)`

    Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

    ## Args

    `bookdata` :`BookData`
      The metadata of the book to be added
    `app_id` :`str`
      The apparat id where the book should be added to
    `prof_id` :`str`
      The id of the professor where the book should be added to.
    Expand source code

        def addBookToDatabase(self, bookdata:BookData,app_id:str, prof_id:str):
            """
            Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

            Args:
                bookdata (BookData): The metadata of the book to be added
                app_id (str): The apparat id where the book should be added to
                prof_id (str): The id of the professor where the book should be added to.
            """
            conn = self.connect()
            cursor = conn.cursor()
            t_query = (
                f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
            )
            # print(t_query)
            result = cursor.execute(t_query).fetchall()
            result = [load_pickle(i[0]) for i in result]
            if bookdata in result:
                print("Bookdata already in database")
                # check if the book was deleted in the apparat
                query = (
                    "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
                )
                params = (app_id, prof_id, dump_pickle(bookdata))
                result = cursor.execute(query, params).fetchone()
                if result[0] == 1:
                    print("Book was deleted, updating bookdata")
                    query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
                    params = (app_id, prof_id, dump_pickle(bookdata))
                    cursor.execute(query, params)
                    conn.commit()
                return

            query = (
                "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
            )
            converted = dump_pickle(bookdata)
            params = (converted, app_id, prof_id, 0)
            cursor.execute(query, params)
            conn.commit()
            self.close_connection(conn)

  `def addMessage(self, message: dict, user, appnr)`
    Expand source code

        def addMessage(self, message:dict,user, appnr):
            def __getUserId(user):
                return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
            user_id = __getUserId(user)
            self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],appnr))

  `def changePassword(self, user, new_password)`
    Expand source code

        def changePassword(self, user, new_password):
            salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
            new_password = salt + new_password
            self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))

  `def checkApparatExists(self, apparat_name)`
    Expand source code

        def checkApparatExists(self, apparat_name):
            return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False

  `def checkApparatExistsById(self, apparat_id)`
    Expand source code

        def checkApparatExistsById(self, apparat_id):
            return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True) else False

  `def checkUsername(self, user)`
    Expand source code

        def checkUsername(self, user):
            data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
            return True if data is not None else False

  `def close_connection(self, conn: sqlite3.Connection)`

    closes the connection to the database

    ## Args:

        - conn (sql.Connection): the connection to be closed

    Expand source code

        def close_connection(self, conn: sql.Connection):
            """
            closes the connection to the database

            Args:
            ----
                - conn (sql.Connection): the connection to be closed
            """
            conn.close()

  `def connect(self) ‑> sqlite3.Connection`

    Connect to the database

    ## Returns

    `sql.Connection`
      The active connection to the database
    Expand source code

        def connect(self)->sql.Connection:
            """
            Connect to the database

            Returns:
                sql.Connection: The active connection to the database
            """
            return sql.connect(self.db_path)

  `def createApparat(self, apparat: src.logic.dataclass.ApparatData) ‑> Union[src.errors.DatabaseErrors.AppPresentError, ForwardRef(None), int]`
    Expand source code

        def createApparat(self, apparat:ApparatData)->Optional[AppPresentError]|int:
            prof_id = self.getProfId(apparat.profname)
            app_id = self.getApparatId(apparat.appname)
            if app_id:
                return AppPresentError(app_id)

            self.createProf(apparat.get_prof_details())
            prof_id = self.getProfId(apparat.profname)
            #ic(prof_id)
            query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
            logger.log_info(query)
            self.query_db(query)
            return self.getApparatId(apparat.appname)

  `def createProf(self, prof_details: dict)`
    Expand source code

        def createProf(self, prof_details:dict):
            prof_title = prof_details["prof_title"]
            prof_fname = prof_details["profname"].split(",")[1]
            prof_fname = prof_fname.strip()
            prof_lname = prof_details["profname"].split(",")[0]
            prof_lname = prof_lname.strip()
            prof_fullname = prof_details["profname"].replace(",", "")
            prof_mail = prof_details["prof_mail"]
            prof_tel = prof_details["prof_tel"]
            params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
            query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
            self.insertInto(query=query, params=params)

  `def createUser(self, user, password, role, salt)`

    Create a user based on passed data.

    ## Args:

        - username (str): Username to be used
        - password (str): the salted password
        - role (str): Role of the user
        - salt (str): a random salt for the user

    Expand source code

        def createUser(self, user, password, role, salt):
            """Create a user based on passed data.

            Args:
            ----
                - username (str): Username to be used
                - password (str): the salted password
                - role (str): Role of the user
                - salt (str): a random salt for the user
            """
            self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))

  `def create_tables(self)`

    Create the tables in the database

    Expand source code

        def create_tables(self):
            """
            Create the tables in the database
            """
            conn = self.connect()
            cursor = conn.cursor()
            cursor.execute(CREATE_TABLE_APPARAT)
            cursor.execute(CREATE_TABLE_MESSAGES)
            cursor.execute(CREATE_TABLE_MEDIA)
            cursor.execute(CREATE_TABLE_APPKONTOS)
            cursor.execute(CREATE_TABLE_FILES)
            cursor.execute(CREATE_TABLE_PROF)
            cursor.execute(CREATE_TABLE_USER)
            cursor.execute(CREATE_TABLE_SUBJECTS)
            conn.commit()
            self.close_connection(conn)

  `def deleteApparat(self, appnr, semester)`
    Expand source code

        def deleteApparat(self, appnr, semester):
            self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,appnr))

  `def deleteBook(self, book_id)`

    Delete a book from the database

    ## Args

    `book_id` :`str`
      ID of the book
    Expand source code

        def deleteBook(self, book_id):
            """
            Delete a book from the database

            Args:
                book_id (str): ID of the book
            """
            self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))

  `def deleteMessage(self, message_id)`
    Expand source code

        def deleteMessage(self, message_id):
            self.query_db("DELETE FROM messages WHERE id=?", (message_id,))

  `def deleteUser(self, user)`
    Expand source code

        def deleteUser(self, user):
            self.query_db("DELETE FROM user WHERE username=?", (user,))

  `def getAllAparats(self, deleted=0)`
    Expand source code

        def getAllAparats(self,deleted=0):
            return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))

  `def getApparatCountBySemester(self) ‑> tuple[list[str], list[int]]`
    Expand source code

        def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
            conn = self.connect()
            cursor = conn.cursor()
            semesters = self.getSemersters()
            created = []
            deleted = []
            for semester in semesters:
                query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
                result = cursor.execute(query).fetchone()
                created.append(result[0])
                query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
                result = cursor.execute(query).fetchone()
                deleted.append(result[0])
            # store data in a tuple
            ret = []
            e_tuple = ()
            for sem in semesters:
                e_tuple = (
                    sem,
                    created[semesters.index(sem)],
                    deleted[semesters.index(sem)],
                )
                ret.append(e_tuple)
            self.close_connection(conn)
            return ret

  `def getApparatData(self, appnr, appname) ‑> src.logic.dataclass.ApparatData`
    Expand source code

        def getApparatData(self, appnr, appname)->ApparatData:

            result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
            if result is None:
                raise NoResultError("No result found")
            apparat = ApparatData()
            apparat.appname = result[1]
            apparat.appnr = result[4]
            apparat.dauerapp = True if result[7] == 1 else False
            prof_data = self.getProfData(self.getProfNameById(result[2]))
            apparat.profname = self.getProfNameById(result[2])
            apparat.prof_mail = prof_data[0]
            apparat.prof_tel = prof_data[1]
            apparat.prof_title = prof_data[2]
            apparat.app_fach = result[3]
            apparat.erstellsemester = result[5]
            apparat.semester = result[8]
            apparat.deleted = result[9]
            apparat.apparat_adis_id = result[11]
            apparat.prof_adis_id = result[12]
            return apparat

  `def getApparatId(self, apparat_name)`
    Expand source code

        def getApparatId(self, apparat_name):
            data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
            if data is None:
                return None
            else:
                return data[0]

  `def getApparatName(self, app_id, prof_id)`
    Expand source code

        def getApparatName(self, app_id, prof_id):
            return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]

  `def getApparatsByProf(self, prof_id: int) ‑> list[tuple]`
    Expand source code

        def getApparatsByProf(self, prof_id:int)->list[tuple]:
            return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))

  `def getApparatsBySemester(self, semester: str) ‑> dict`
    Expand source code

        def getApparatsBySemester(self, semester:str)->dict:
            data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
            conn = self.connect()
            cursor = conn.cursor()
            c_tmp = []
            for i in data:
                c_tmp.append((i[0], self.getProfNameById(i[1])))
            query = (
                f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
            )
            result = cursor.execute(query).fetchall()
            d_tmp = []
            for i in result:
                d_tmp.append((i[0], self.getProfNameById(i[1])))
            # group the apparats by prof
            c_ret = {}
            for i in c_tmp:
                if i[1] not in c_ret.keys():
                    c_ret[i[1]] = [i[0]]
                else:
                    c_ret[i[1]].append(i[0])
            d_ret = {}
            for i in d_tmp:
                if i[1] not in d_ret.keys():
                    d_ret[i[1]] = [i[0]]
                else:
                    d_ret[i[1]].append(i[0])
            self.close_connection(conn)
            return {"created": c_ret, "deleted": d_ret}

  `def getBlob(self, filename, app_id)`

    Get a blob from the database

    ## Args

    `filename` :`str`
      The name of the file
    `app_id` :`str`
      ID of the apparat

    ## Returns

    `bytes`
      The file stored in
    Expand source code

        def getBlob(self, filename, app_id):
            """
            Get a blob from the database

            Args:
                filename (str): The name of the file
                app_id (str): ID of the apparat

            Returns:
                bytes: The file stored in
            """
            return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]

  `def getBook(self, book_id: int) ‑> src.logic.dataclass.BookData`

    Get the book based on the id in the database

    ## Args

    `book_id` :`int`
      The id of the book

    ## Returns

    `BookData`
      The metadata of the book wrapped in a BookData object
    Expand source code

        def getBook(self,book_id:int)->BookData:
            """
            Get the book based on the id in the database

            Args:
                book_id (int): The id of the book

            Returns:
                BookData: The metadata of the book wrapped in a BookData object
            """
            return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])

  `def getBookBasedOnSignature(self, app_id: str, prof_id: str, signature: str) ‑> src.logic.dataclass.BookData`

    Get the book based on the signature of the book.

    ## Args

    `app_id` :`str`
      The apparat id the book should be associated with
    `prof_id` :`str`
      The professor id the book should be associated with
    `signature` :`str`
      The signature of the book

    ## Returns

    `BookData`
      The total metadata of the book wrapped in a BookData object
    Expand source code

        def getBookBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->BookData:
            """
            Get the book based on the signature of the book.

            Args:
                app_id (str): The apparat id the book should be associated with
                prof_id (str): The professor id the book should be associated with
                signature (str): The signature of the book

            Returns:
                BookData: The total metadata of the book wrapped in a BookData object
            """
            result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
            books = [load_pickle(i[0]) for i in result]
            book = [i for i in books if i.signature == signature][0]
            return book

  `def getBookId(self, bookdata: src.logic.dataclass.BookData, app_id, prof_id) ‑> int`

    Get the id of a book based on the metadata of the book

    ## Args

    `bookdata` :`BookData`
      The wrapped metadata of the book
    `app_id` :`str`
      The apparat id the book should be associated with
    `prof_id` :`str`
      The professor id the book should be associated with

    ## Returns

    `int`
      ID of the book
    Expand source code

        def getBookId(self, bookdata:BookData, app_id, prof_id)->int:
            """
            Get the id of a book based on the metadata of the book

            Args:
                bookdata (BookData): The wrapped metadata of the book
                app_id (str): The apparat id the book should be associated with
                prof_id (str): The professor id the book should be associated with

            Returns:
                int: ID of the book
            """
            result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
            return result[0]

  `def getBookIdBasedOnSignature(self, app_id: str, prof_id: str, signature: str) ‑> int`

    Get a book id based on the signature of the book.

    ## Args

    `app_id` :`str`
      The apparat id the book should be associated with
    `prof_id` :`str`
      The professor id the book should be associated with
    `signature` :`str`
      The signature of the book

    ## Returns

    `int`
      The id of the book
    Expand source code

        def getBookIdBasedOnSignature(self, app_id:str, prof_id:str,signature:str)->int:
            """
            Get a book id based on the signature of the book.

            Args:
                app_id (str): The apparat id the book should be associated with
                prof_id (str): The professor id the book should be associated with
                signature (str): The signature of the book

            Returns:
                int: The id of the book
            """
            result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
            books = [(load_pickle(i[0]),i[1]) for i in result]
            book = [i for i in books if i[0].signature == signature][0][1]
            return book

  `def getBooks(self, app_id, prof_id, deleted=0) ‑> list[dict[int, src.logic.dataclass.BookData, int]]`

    Get the Books based on the apparat id and the professor id

    ## Args

    `app_id` :`str`
      The ID of the apparat
    `prof_id` :`str`
      The ID of the professor
    `deleted` :`int`, optional
      The state of the book. Set to 1 to include deleted ones. Defaults to 0.

    ## Returns

    `list[dict[int, BookData, int]]`
      A list of dictionaries containing the id, the metadata of the book and the availability of the book
    Expand source code

        def getBooks(self, app_id, prof_id, deleted=0)->list[dict[int, BookData, int]]:
            """
            Get the Books based on the apparat id and the professor id

            Args:
                app_id (str): The ID of the apparat
                prof_id (str): The ID of the professor
                deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.

            Returns:
                list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
            """
            qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
            ret_result = []
            for result_a in qdata:
                data = {"id": int, "bookdata": BookData, "available": int}
                data["id"] = result_a[0]
                data["bookdata"] = load_pickle(result_a[1])
                data["available"] = result_a[2]
                ret_result.append(data)
            return ret_result

  `def getFacultyMember(self, name: str)`
    Expand source code

        def getFacultyMember(self, name:str):
            return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)

  `def getFacultyMembers(self)`
    Expand source code

        def getFacultyMembers(self):
            return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")

  `def getFiles(self, app_id: int, prof_id: int) ‑> list[tuple]`
    Expand source code

        def getFiles(self, app_id:int, prof_id:int)->list[tuple]:
            return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))

  `def getLastBookId(self) ‑> int`

    Get the last book id in the database

    ## Returns

    `int`
      ID of the last book in the database
    Expand source code

        def getLastBookId(self)->int:
            """
            Get the last book id in the database

            Returns:
                int: ID of the last book in the database
            """
            return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]

  `def getMessages(self, date: str)`
    Expand source code

        def getMessages(self, date:str):
            def __get_user_name(user_id):
                return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
            messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
            ret = [
                {
                    "message": i[2],
                    "user": __get_user_name(i[4]),
                    "appnr": i[5],
                    "id": i[0]
                }
                for i in messages
            ]
            return ret

  `def getProfByName(self, prof_name: str)`
    Expand source code

        def getProfByName(self, prof_name:str):
            return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)

  `def getProfData(self, profname: str)`
    Expand source code

        def getProfData(self, profname:str):

            data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
            return data

  `def getProfId(self, prof_name: str)`

    getProfId summary

    :param prof_name: description :type prof_name: str :return: description :rtype: type

    Expand source code

        def getProfId(self, prof_name:str):
            """
            getProfId _summary_

            :param prof_name: _description_
            :type prof_name: str
            :return: _description_
            :rtype: _type_
            """
            data = self.getProfByName(prof_name.replace(",", ""))
            if data is None:
                return None
            else:
                return data[0]

  `def getProfNameById(self, prof_id: int, add_title: bool = False)`
    Expand source code

        def getProfNameById(self, prof_id:int,add_title:bool=False):
            prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
            if add_title:
                return f"{self.getTitleById(prof_id)}{prof[0]}"
            else:
                return prof[0]

  `def getProfs(self)`
    Expand source code

        def getProfs(self):
            return self.query_db("SELECT * FROM prof")

  `def getRole(self, user)`
    Expand source code

        def getRole(self, user):
            return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]

  `def getRoles(self)`
    Expand source code

        def getRoles(self):
            return self.query_db("SELECT role FROM user")

  `def getSemersters(self)`
    Expand source code

        def getSemersters(self):
            data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
            return [i[0] for i in data]

  `def getSpecificProfData(self, prof_id: int, fields: List[str])`

    getSpecificProfData summary

    ## Args:

        - prof_id (int): _description_
        - fields (List[str]): _description_

    ## Returns:

        - _type_: _description_

    Expand source code

        def getSpecificProfData(self, prof_id:int, fields:List[str]):
            """
            getSpecificProfData _summary_

            Args:
            ----
                - prof_id (int): _description_
                - fields (List[str]): _description_

            Returns:
            -------
                - _type_: _description_
            """
            query = "SELECT "
            for field in fields:
                query += f"{field},"
            query = query[:-1]
            query += " FROM prof WHERE id=?"
            return self.query_db(query, (prof_id,), one=True)[0]

  `def getSubjects(self)`
    Expand source code

        def getSubjects(self):
            return self.query_db("SELECT * FROM subjects")

  `def getTitleById(self, prof_id: int)`
    Expand source code

        def getTitleById(self, prof_id:int):
            title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
            return f"{title} " if title is not None else ""

  `def getUnavailableApparatNumbers(self) ‑> List[int]`

    getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers

    ## Returns:

        - number(List[int]): a list of all currently used apparat numbers

    Expand source code

        def getUnavailableApparatNumbers(self)->List[int]:
            """
            getUnavailableApparatNumbers returns a list of all currently used ApparatNumbers

            Returns:
            -------
                - number(List[int]): a list of all currently used apparat numbers
            """
            numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")
            numbers = [i[0] for i in numbers]
            logger.log_info(f"Currently used apparat numbers: {numbers}")
            return numbers

  `def getUser(self)`
    Expand source code

        def getUser(self):
            return self.query_db("SELECT * FROM user", one=True)

  `def getUsers(self)`
    Expand source code

        def getUsers(self):
            return self.query_db("SELECT * FROM user")

  `def get_db_contents(self) ‑> Optional[List[Tuple]]`

    Get the contents of the

    ## Returns

    `Union[List[Tuple], None]`
      description
    Expand source code

        def get_db_contents(self)->Union[List[Tuple], None]:
            """
            Get the contents of the

            Returns:
                Union[List[Tuple], None]: _description_
            """
            try:
                with sql.connect(self.db_path) as conn:
                    cursor = conn.cursor()
                    cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
                    return cursor.fetchall()
            except sql.OperationalError:
                return None

  `def insertFile(self, file: list[dict], app_id: int, prof_id)`
    Expand source code

        def insertFile(self, file: list[dict], app_id: int, prof_id):
            for f in file:
                filename = f["name"]
                path = f["path"]
                filetyp = f["type"]
                if path == "Database":
                    continue
                blob = create_blob(path)
                query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
                self.query_db(query, (filename, blob, app_id, filetyp,prof_id))

  `def insertInto(self, query: str, params: Tuple) ‑> None`

    Insert sent data into the database

    ## Args

    `query` :`str`
      The query to be executed
    `params` :`Tuple`
      the parameters to be inserted into the database
    Expand source code

        def insertInto(self, query:str, params:Tuple) -> None:
            """
            Insert sent data into the database

            Args:
                query (str): The query to be executed
                params (Tuple): the parameters to be inserted into the database
            """
            conn = self.connect()
            cursor = conn.cursor()
            logger.log_info(f"Inserting {params} into database with query {query}")
            cursor.execute(query, params)
            conn.commit()
            self.close_connection(conn)

  `def isEternal(self, id)`
    Expand source code

        def isEternal(self, id):
            return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)

  `def login(self, user, hashed_password)`
    Expand source code

        def login(self, user, hashed_password):
            salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
            if salt is None:
                return False
            hashed_password = salt + hashed_password
            password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
            if password == hashed_password:
                return True
            else:
                return False

  `def query_db(self, query: str, args: Tuple = (), one: bool = False) ‑> Union[Tuple, List[Tuple]]`

    Query the Database for the sent query.

    ## Args

    `query` :`str`
      The query to be executed
    `args` :`Tuple`, optional
      The arguments for the query. Defaults to ().
    `one` :`bool`, optional
      Return the first result only. Defaults to False.

    ## Returns

    Union[Typle|List[Tuple]]: Returns the result of the query

    Expand source code

        def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
            """
            Query the Database for the sent query.

            Args:
                query (str): The query to be executed
                args (Tuple, optional): The arguments for the query. Defaults to ().
                one (bool, optional): Return the first result only. Defaults to False.

            Returns:
                Union[Typle|List[Tuple]]: Returns the result of the query
            """
            conn = self.connect()
            cursor = conn.cursor()
            logger.log_info(f"Querying database with query {query}, args: {args}")
            cursor.execute(query, args)
            rv = cursor.fetchall()
            conn.commit()
            self.close_connection(conn)
            return (rv[0] if rv else None) if one else rv

  `def recreateFile(self, filename, app_id, filetype)`
    Expand source code

        def recreateFile(self, filename, app_id,filetype):
            blob = self.getBlob(filename, app_id)
            tempdir = config.database.tempdir
            tempdir = tempdir.replace("~", str(Path.home()))
            tempdir_path = Path(tempdir)
            if not os.path.exists(tempdir_path):
                os.mkdir(tempdir_path)
            file = tempfile.NamedTemporaryFile(
                delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
            )
            file.write(blob)
            print("file created")
            return file.name

  `def searchBook(self, data: dict[str, str]) ‑> list[tuple[src.logic.dataclass.BookData, int]]`

    Search a book in the database based on the sent data.

    ## Args

    `data` :`dict[str, str]`
      A dictionary containing the data to be searched for. The dictionary can contain the following:
    - signature: The signature of the book
    - title: The title of the book

    ## Returns

    `list[tuple[BookData, int]]`
      A list of tuples containing the wrapped Metadata and the id of the book
    Expand source code

        def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
            """
            Search a book in the database based on the sent data.

            Args:
                data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following:
                - signature: The signature of the book
                - title: The title of the book

            Returns:
                list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
            """
            rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
            #ic(rdata, len(rdata))
            mode = 0
            if len(data)== 1:
                if "signature" in data.keys():
                    mode = 1
                elif "title" in data.keys():
                    mode = 2
            elif len(data) == 2:
                mode = 3
            else:
                return None
            ret = []
            for book in rdata:
                bookdata = load_pickle(book[1])
                app_id = book[2]
                prof_id = book[3]
                if mode == 1:
                    if data["signature"] in bookdata.signature:
                        ret.append((bookdata,app_id,prof_id))
                elif mode == 2:
                    if data["title"] in bookdata.title:
                        ret.append((bookdata,app_id,prof_id))
                elif mode == 3:
                    if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
                        ret.append((bookdata,app_id,prof_id))
            #ic(ret)
            return ret

  `def setAvailability(self, book_id: str, available: str)`

    Set the availability of a book in the database

    ## Args

    `book_id` :`str`
      The id of the book
    `available` :`str`
      The availability of the book
    Expand source code

        def setAvailability(self, book_id:str, available:str):
            """
            Set the availability of a book in the database

            Args:
                book_id (str): The id of the book
                available (str): The availability of the book
            """
            self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))

  `def setNewSemesterDate(self, appnr, newDate, dauerapp=False)`
    Expand source code

        def setNewSemesterDate(self, appnr, newDate, dauerapp=False):
            date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
            if dauerapp:
                self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,appnr))
            else:
                self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,appnr))

  `def statistic_request(self, **kwargs: Any)`
    Expand source code

        def statistic_request(self, **kwargs: Any):
            def __query(query):
                conn = self.connect()
                cursor = conn.cursor()
                result = cursor.execute(query).fetchall()
                for result_a in result:
                    orig_value = result_a
                    prof_name = self.getProfNameById(result_a[2])
                    # replace the prof_id with the prof_name
                    result_a = list(result_a)
                    result_a[2] = prof_name
                    result_a = tuple(result_a)
                    result[result.index(orig_value)] = result_a
                self.close_connection(conn)
                return result
            if "deletable" in kwargs.keys():
                query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
                return __query(query)
            if "dauer" in kwargs.keys():
                kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
            query = "SELECT * FROM semesterapparat WHERE "
            for key, value in kwargs.items() if kwargs.items() is not None else {}:
                print(key, value)
                query += f"{key}='{value}' AND "
                print(query)
            # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
            if "deletesemester" in kwargs.keys():
                query = query.replace(
                    f"deletesemester='{kwargs['deletesemester']}' AND ", ""
                )
            if "endsemester" in kwargs.keys():
                if "erstellsemester" in kwargs.keys():
                    query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
                    query = query.replace(
                        f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
                    )
                else:
                    query = query.replace(
                        f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
                    )
                    print("replaced")
                query = query.replace(
                    "xyz",
                    f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
                )
            # remove all x="" parts from the query where x is a key in kwargs
            query = query[:-5]
            print(query)
            return __query(query)

  `def updateApparat(self, apparat_data: src.logic.dataclass.ApparatData)`
    Expand source code

        def updateApparat(self, apparat_data:ApparatData):
            query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
            params = (
                apparat_data.appname,
                apparat_data.app_fach,
                apparat_data.dauerapp,
                self.getProfId(apparat_data.profname),
                apparat_data.appnr,
            )
            self.query_db(query, params)

  `def updateBookdata(self, book_id, bookdata: src.logic.dataclass.BookData)`

    Update the bookdata in the database

    ## Args

    `book_id` :`str`
      The id of the book
    `bookdata` :`BookData`
      The new metadata of the book
    Expand source code

        def updateBookdata(self, book_id, bookdata:BookData):
            """
            Update the bookdata in the database

            Args:
                book_id (str): The id of the book
                bookdata (BookData): The new metadata of the book
            """
            self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))

  `def updateFacultyMember(self, data, oldlname, oldfname)`
    Expand source code

        def updateFacultyMember(self, data, oldlname, oldfname):
            placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
            query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
            data["oldlname"] = oldlname
            data["oldfname"] = oldfname
            self.query_db(query, data)

  `def updateUser(self, username, data: dict[str, str])`
    Expand source code

        def updateUser(self, username, data:dict[str, str]):
            conn = self.connect()
            cursor = conn.cursor()
            query = "UPDATE user SET "
            for key,value in data.items():
                if key == "username":
                    continue
                query += f"{key}='{value}',"
            query = query[:-1]
            query += " WHERE username=?"
            params = (username,)
            cursor.execute(query, params)
            conn.commit()
            self.close_connection(conn)