--- a/files/hedgedoc-util.py	2024-02-03 23:19:47.720921062 +0100
+++ b/files/hedgedoc-util.py	2024-02-03 23:19:42.926928390 +0100
@@ -7,9 +7,7 @@
 from subprocess import Popen, PIPE
 
 import click
-import pymysql
-import pymysql.cursors
-import configparser
+import psycopg2
 
 class GlobalState():
 	def __init__(self, options):
@@ -21,23 +19,17 @@
 		except Exception as e:
 			click.echo("Database connection failed: {}".format(repr(e)))
 			sys.exit(2)
-		self._check_schema()
 
 	def _get_connection(self):
-		return pymysql.connect(host=self.config['dbhost'],
-			user=self.config['dbuser'],
-			password=self.config['dbpw'],
-			database=self.config['dbname'],
-			charset='utf8mb4',
-			cursorclass=pymysql.cursors.DictCursor)
+		dsn = "host="+self.config['dbhost']
+		if 'dbuser' in self.config:
+			dsn += "user="+self.config['dbuser']
+		if 'dbpw' in self.config:
+			dsn += "password="+self.config['dbpw']
+		if 'dbname' in self.config:
+			dsn += "dbname="+self.config['dbname']
 
-	def _check_schema(self):
-		with self.db.cursor() as cursor:
-			cursor.execute('SELECT name from SequelizeMeta ORDER BY name ASC')
-			schema = ','.join([i['name'] for i in cursor.fetchall()])
-		if schema != self.config['dbschema']:
-			click.echo("Unsupportet db schema: {}".format(schema))
-			sys.exit(2)
+		return psycopg2.connect(dsn)
 
 	def _load_config(self, path):
 		result = {}
@@ -69,6 +61,9 @@
 
 def _decode_nested_json(data, fieldnames):
 	for i in data:
+		if i == None:
+			continue
+
 		for fieldname in fieldnames:
 			if fieldname in i:
 				if i[fieldname] == None:
@@ -84,34 +79,40 @@
 def note_id_encode_to_url(input_id):
 	return base64.urlsafe_b64encode(binascii.unhexlify(input_id.replace('-', '').encode())).decode().replace('=', '')
 
-def pad_list(db, columns, last_change_older=0, owner=0):
+def pad_list(db, columns, last_change_older, owner):
+	if owner == None:
+		owner = "00000000-0000-0000-0000-000000000000"
 	with db.cursor() as cursor:
 		# this is no sql injection vulnerability because we let click verify the content of "columns" to match a whitelist
-		cursor.execute(F"SELECT {','.join(columns)} FROM Notes WHERE (%(last_change_older)s = 0 OR DATEDIFF(NOW(), lastchangeAt) > %(last_change_older)s OR (lastchangeAt IS NULL AND DATEDIFF(NOW(), createdAt) > %(last_change_older)s) AND (%(owner)s = '' OR ownerId = %(owner)s)) ORDER BY id", {'last_change_older': last_change_older, 'owner': owner})
+		cursor.execute(F"SELECT {','.join(columns)} FROM \"Notes\" WHERE (%(last_change_older)s = 0 OR NOW() - \"lastchangeAt\" > interval '%(last_change_older)s' OR (\"lastchangeAt\" IS NULL AND NOW() - \"createdAt\" > interval '%(last_change_older)s') AND (%(owner)s = '' OR \"ownerId\" = %(owner)s::uuid)) ORDER BY id", {'last_change_older': last_change_older, 'owner': owner})
 		return _decode_nested_json(cursor.fetchall(), ['authorship'])
 
 def pad_get(db, id):
 	with db.cursor() as cursor:
-		cursor.execute('SELECT * FROM Notes WHERE id=%s', (id))
+		cursor.execute('SELECT * FROM "Notes" WHERE id=%s', (id,))
 		return _decode_nested_json([cursor.fetchone()], ['authorship'])[0]
 
 def pad_get_content(db, id):
 	return pad_get(db, id).get('content', '')
 
-def pad_delete(db, id):
-	pad = pad_get(db, id)
-	urlid = note_id_encode_to_url(id)
-	with db:
-		with db.cursor() as cursor:
-			cursor.execute('DELETE FROM Revisions WHERE noteId=%s', (id))
-			cursor.execute('DELETE FROM Notes WHERE id=%s', (id))
-			cursor.execute('SELECT id,history FROM Users WHERE JSON_SEARCH(history, "one", %s, "", "$[*].id") is not null;', (urlid))
-			with db.cursor() as usercursor:
-				for i in cursor:
-					history = json.loads(i['history'] or '[]')
-					history = [ j for j in history if not j.get('id') == urlid ]
-					usercursor.execute('UPDATE Users set history=%s WHERE id=%s;', (json.dumps(history), i['id']))
-		db.commit()
+def pad_delete(db, ids):
+    for id in ids:
+        if id == "--":
+            continue
+
+        pad = pad_get(db, id)
+        urlid = note_id_encode_to_url(id)
+        with db:
+            with db.cursor() as cursor:
+                cursor.execute('DELETE FROM "Revisions" WHERE "noteId"=%s', (id,))
+                cursor.execute('DELETE FROM "Notes" WHERE id=%s', (id,))
+                cursor.execute('SELECT id,history FROM "Users" u, json_array_elements(u.history::json) h WHERE h->>\'id\'=%s is not null;', (urlid,))
+                with db.cursor() as usercursor:
+                    for i in cursor:
+                        history = json.loads(i[1] or '[]')
+                        history = [ j for j in history if not j.get('id') == urlid ]
+                        usercursor.execute('UPDATE "Users" set history=%s WHERE id=%s;', (json.dumps(history), i[0]))
+            db.commit()
 
 def pad_mail(db, id, template, formats):
 	with db.cursor() as cursor:
@@ -144,12 +145,12 @@
 def user_list(db, columns):
 	with db.cursor() as cursor:
 		# this is no sql injection vulnerability because we let click verify the content of "columns" to match a whitelist
-		cursor.execute('SELECT {} FROM Users ORDER BY id'.format(','.join(columns)))
+		cursor.execute('SELECT {} FROM "Users" ORDER BY id'.format(','.join(columns)))
 		return _decode_nested_json(cursor.fetchall(), ['profile', 'history'])
 
 def user_get(db, id):
 	with db.cursor() as cursor:
-		cursor.execute('SELECT * FROM Users WHERE id=%s', (id))
+		cursor.execute('SELECT * FROM "Users" WHERE id=%s', (id,))
 		return _decode_nested_json([cursor.fetchone()], ['profile', 'history'])[0]
 
 def user_get_mail(db, id):
@@ -192,12 +193,11 @@
 
 @click.group()
 @click.option('-o', '--output', type=click.Choice(['text', 'json', 'tsv', 'tsv-noheader']), default='text', help='Select output format', show_default=True, show_envvar=True)
-@click.option('--config', default='/usr/local/etc/hedgedoc-util/hedgedoc-util.cfg', type=click.Path(), help='Config to load db and template default settings from', show_envvar=True, show_default=True)
+@click.option('--config', default='/etc/hedgedoc-util/hedgedoc-util.cfg', type=click.Path(), help='Config to load db and template default settings from', show_envvar=True, show_default=True)
 @click.option('--dbuser', help='User name used for the db connection', show_envvar=True)
 @click.option('--dbpw', help='Password used for the db connection', show_envvar=True)
 @click.option('--dbname', help='Database used', show_envvar=True)
-@click.option('--dbhost', help='Host the db is running on', show_envvar=True)
-@click.option('--dbschema', help='Schema string to verify the db schema against', show_envvar=True)
+@click.option('--dbhost', help='Host the db is running on', default='/run/postgresql/', show_envvar=True)
 @click.pass_context
 def cli(ctx, **kwargs):
 	ctx.obj = GlobalState(kwargs)
@@ -219,7 +219,7 @@
 @cli_pad.command(name="list", help="List all pads")
 @click.option('-c', '--columns', default=['id'], type=click.Choice(['id', 'title', 'content', 'ownerId', 'createdAt', 'updatedAt', 'shortid', 'permission', 'viewcount', 'lastchangeuserId', 'lastchangeAt', 'alias', 'deletedAt', 'authorship']), help="Select what data to display. Can be passed multiple times.", multiple=True, show_default=True, show_envvar=True)
 @click.option('--last-change-older', type=click.INT, default=0, help='Only list those pads which are older than this value. In days.', show_envvar=True)
-@click.option('--owner', type=click.STRING, default='', help='Only list pads with this owner, pass the user id', show_envvar=True)
+@click.option('--owner', type=click.STRING, help='Only list pads with this owner, pass the user id', show_envvar=True)
 @click.pass_obj
 def _pad_list(obj, columns, last_change_older, owner):
 	output_object(pad_list(obj.db, columns, last_change_older=last_change_older, owner=owner))
@@ -231,10 +231,10 @@
 	output_object(pad_get(obj.db, id))
 
 @cli_pad.command(name="delete", help="Deletes a pad")
-@click.argument('id')
+@click.argument('ids', nargs=-1)
 @click.pass_obj
-def _pad_delete(obj, id):
-	pad_delete(obj.db, id)
+def _pad_delete(obj, ids):
+	pad_delete(obj.db, ids)
 
 @cli_pad.command(name="get-content", help="Get the content of one pad by its id")
 @click.argument('id')
