diff --git a/cutprogress.py b/cutprogress.py
index e08b9b5ac19d466e5af28b5399f42c3b574cca44..1d8a238674ab8be87ef6957564592d696d515035 100644
--- a/cutprogress.py
+++ b/cutprogress.py
@@ -40,15 +40,21 @@ def cutprogress(user=None):
 				lectures.course_id,
 				lectures.time,
 				lectures.title,
-				COUNT(videos.id) as videos_total,
-				COUNT(videos.visible) as videos_visible
+				video_counts.videos_total,
+				video_counts.videos_visible
 			FROM lectures
-			JOIN courses ON courses.id = lectures.course_id
-			LEFT JOIN videos ON lectures.id = videos.lecture_id
+			JOIN courses ON ( courses.id = lectures.course_id )
+			LEFT JOIN (
+					SELECT
+						videos.lecture_id,
+						COUNT(videos.id) as videos_total,
+						COUNT(videos.visible) as videos_visible
+					FROM videos
+					GROUP BY videos.lecture_id
+				) AS video_counts ON ( video_counts.lecture_id = lectures.id )
 			WHERE courses.id = ?
 				AND lectures.time <= ?
 				AND NOT lectures.norecording
-			GROUP BY lectures.id
 			ORDER BY lectures.time ASC, lectures.id ASC
 			''', course['id'], datetime.now())
 	# Generate list of days, figure out when weeks change
diff --git a/db.py b/db.py
index 583035c9336b4f33e2406395c8fddd833da3987d..2095b679ec6a640e7468ddf528ceb3445650e1ba 100644
--- a/db.py
+++ b/db.py
@@ -148,20 +148,3 @@ def close_db(*args): #pylint: disable=unused-argument
 	if 'db' in g:
 		g.db.close()
 		del g.db
-
-def searchquery(text, columns, match, tables, suffix, *suffixparams):
-	params = []
-	subexprs = []
-	words = text.split(' ')
-	prio = len(words)+1
-	for word in words:
-		if word == '' or word.isspace():
-			continue
-		matchexpr = ' OR '.join(['%s LIKE ?'%column for column in match])
-		subexprs.append('SELECT %s, %s AS _prio FROM %s WHERE %s'%(columns, str(prio), tables, matchexpr))
-		params += ['%'+word+'%']*len(match)
-		prio -= 1
-	if subexprs == []:
-		return []
-	expr = 'SELECT *,SUM(_prio) AS _score FROM (%s) AS _tmp %s'%(' UNION '.join(subexprs), suffix)
-	return query(expr, *(list(params)+list(suffixparams)))
diff --git a/edit.py b/edit.py
index 6da7d39ff231798cd1b5512a8ec173020984dbc4..085a4b403e5455ad907f7831c21eb78c7e7b463e 100644
--- a/edit.py
+++ b/edit.py
@@ -255,7 +255,8 @@ def changelog():
 @csrf_protect
 def set_responsible(course_id, user_id, value):
 	if value:
-		modify('REPLACE INTO responsible (course_id, user_id) values (?, ?)', course_id, user_id)
+		if not query('SELECT id FROM responsible WHERE course_id = ? AND user_id = ?', course_id, user_id):
+			modify('INSERT INTO responsible (course_id, user_id) VALUES (?, ?)', course_id, user_id)
 	else:
 		modify('DELETE FROM responsible WHERE course_id = ? AND user_id = ?', course_id, user_id)
 	return "OK", 200
diff --git a/jobs.py b/jobs.py
index 13f64ff05453e72596d70db106cedeaeddc6b801..e32bbe7c0231eb70970590f7417798ae783316e1 100644
--- a/jobs.py
+++ b/jobs.py
@@ -87,7 +87,10 @@ def jobs_ping(id):
 @app.route('/internal/jobs/api/worker/<hostname>/schedule', methods=['POST'])
 @api_token_required('JOBS_API_KEY')
 def jobs_schedule(hostname):
-	query('REPLACE INTO worker (hostname, last_ping) values (?, ?)', hostname, datetime.now())
+	if query("SELECT hostname FROM worker WHERE hostname = ?", hostname):
+		query("UPDATE worker SET last_ping = ? WHERE hostname = ?", datetime.now(), hostname)
+	else:
+		query("INSERT INTO worker (hostname, last_ping) VALUES (?, ?)", hostname, datetime.now())
 	hostdata = request.get_json()
 	if not hostdata:
 		return 'no hostdata sent', 400
diff --git a/livestreams.py b/livestreams.py
index b7e7cc3eb386b8a7a62f3ccac3ea8d94fbba83a6..dcb0e3070ef9c4decdf0baa19ca7edde76a02050 100644
--- a/livestreams.py
+++ b/livestreams.py
@@ -44,10 +44,8 @@ def streamauth_legacy(server=None):
 				break
 		if 'lecture' in request.values:
 			match = {'id': request.values['lecture']}
-		try:
+		if not query("SELECT handle FROM streams WHERE handle = ?", request.values['name']):
 			modify("INSERT INTO streams (handle, active, visible, lecture_id, description, poster) VALUES (?, false, true, -1, '', '')", request.values['name'])
-		except:
-			pass
 		if server:
 			data = {'src': 'rtmp://%s/live/%s'%(server, request.values['name']),
 				'destbase': 'rtmp://%s/hls/%s'%(server, request.values['name'])}
diff --git a/server.py b/server.py
index ab43eb8edf265404e46f930b662639f60f6efe63..845fa30f9a85ce6a353ead5b58c36cb5eb8136e4 100644
--- a/server.py
+++ b/server.py
@@ -71,7 +71,7 @@ def evalperm(perms):
 	return [{'type': 'public'}]
 
 #pylint: disable=wrong-import-position
-from db import query, modify, show, searchquery
+from db import query, modify, show
 from template_helper import *
 from mail import notify_mods, notify_admins #pylint: disable=unused-import
 from ldap import ldapauth
@@ -171,13 +171,21 @@ def index():
 		i['date'] = i['time'].date()
 	latestvideos = query('''
 		SELECT lectures.*, \'course\' AS sep, courses.*
-		FROM lectures
-		LEFT JOIN videos ON (videos.lecture_id = lectures.id)
-		LEFT JOIN courses on (courses.id = lectures.course_id)
-		WHERE (? OR (courses.visible AND courses.listed AND lectures.visible AND videos.visible))
-		GROUP BY videos.lecture_id
-		ORDER BY MAX(videos.time_created) DESC
-		LIMIT 6	''', ismod())
+		FROM (
+				SELECT
+					videos.lecture_id,
+					MAX(videos.time_created) AS _time_publish
+				FROM videos
+				JOIN lectures ON ( lectures.id = videos.lecture_id )
+				JOIN courses ON ( courses.id = lectures.course_id )
+				WHERE (? OR (courses.visible AND courses.listed AND lectures.visible AND videos.visible))
+				GROUP BY videos.lecture_id
+				ORDER BY _time_publish DESC
+				LIMIT 6
+			) AS _latest
+		JOIN lectures ON ( lectures.id = _latest.lecture_id )
+		JOIN courses ON ( courses.id = lectures.course_id )
+		''', ismod())
 	livestreams = query('''SELECT streams.handle AS livehandle, lectures.*, \'course\' AS sep, courses.*
 		FROM streams
 		JOIN lectures ON lectures.id = streams.lecture_id
@@ -247,7 +255,7 @@ def course(id=None, handle=None):
 	for i in query('SELECT lectures.id AS id, COUNT(chapters.id) AS c FROM chapters \
 			JOIN lectures ON chapters.lecture_id = lectures.id \
 			WHERE lectures.course_id = ? AND NOT chapters.visible AND NOT chapters.deleted \
-			GROUP BY chapters.lecture_id;', course['id']):
+			GROUP BY lectures.id;', course['id']):
 		chapters[i['id']] = i['c']
 	lectures = query('SELECT * FROM lectures WHERE course_id = ? AND (? OR visible) ORDER BY time, duration DESC', course['id'], ismod())
 	for lecture in lectures:
@@ -395,31 +403,8 @@ def search():
 	if 'q' not in request.args:
 		return redirect(url_for('index'))
 	searchtext = request.args['q']
-	courses = searchquery(searchtext, '*', ['title', 'short', 'organizer', 'subject', 'description'],
-			'courses', 'WHERE (? OR (visible AND listed)) GROUP BY id ORDER BY _score DESC, semester DESC LIMIT 20', ismod())
-	lectures = searchquery(searchtext, 'lectures.*, \
-			courses.visible AS coursevisible, \
-			courses.listed, \
-			courses.id AS courses_id, \
-			courses.visible AS courses_visible, \
-			courses.listed AS courses_listed, \
-			courses.title AS courses_title, \
-			courses.short AS courses_short, \
-			courses.handle AS courses_handle, \
-			courses.organizer AS courses_organizer, \
-			courses.subject AS courses_subject, \
-			courses.credits AS courses_credits, \
-			courses.created_by AS courses_created_by, \
-			courses.time_created AS courses_time_created, \
-			courses.time_updated AS courses_time_updated, \
-			courses.semester AS courses_semester, \
-			courses.downloadable AS courses_downloadable, \
-			courses.embedinvisible AS courses_embedinvisible, \
-			courses.description AS courses_description, \
-			courses.internal AS courses_internal',
-			['lectures.title', 'lectures.comment', 'lectures.speaker', 'courses.short'],
-			'lectures LEFT JOIN courses on (courses.id = lectures.course_id)',
-			'WHERE (? OR (coursevisible AND listed AND visible)) GROUP BY id ORDER BY _score DESC, time DESC LIMIT 30', ismod())
+	courses = _course_query_search(searchtext, ismod())
+	lectures = _lecture_query_search(searchtext, ismod())
 	for lecture in lectures:
 		lecture['course'] = {}
 		for key in lecture:
@@ -427,6 +412,99 @@ def search():
 				lecture['course'][key[8:]] = lecture[key]
 	return render_template('search.html', searchtext=searchtext, courses=courses, lectures=lectures)
 
+
+# This search is basically stolen from the new api
+
+def _course_query_search(search_term: str, is_mod: bool):
+	return _query_search(
+		"courses",
+		["title", "short", "organizer", "subject", "description"],
+		None,
+		None,
+		None if is_mod else 'WHERE "courses"."visible" AND "courses"."listed"',
+		'"courses"."semester" DESC',
+		20,
+		search_term
+	)
+
+
+def _lecture_query_search(search_term: str, is_mod: bool):
+	return _query_search(
+		"lectures",
+		["title", "comment", "speaker"],
+		'JOIN "courses" ON ("lectures"."course_id" = "courses"."id")',
+		"""\
+courses.visible AS coursevisible, \
+courses.listed, \
+courses.id AS courses_id, \
+courses.visible AS courses_visible, \
+courses.listed AS courses_listed, \
+courses.title AS courses_title, \
+courses.short AS courses_short, \
+courses.handle AS courses_handle, \
+courses.organizer AS courses_organizer, \
+courses.subject AS courses_subject, \
+courses.credits AS courses_credits, \
+courses.created_by AS courses_created_by, \
+courses.time_created AS courses_time_created, \
+courses.time_updated AS courses_time_updated, \
+courses.semester AS courses_semester, \
+courses.downloadable AS courses_downloadable, \
+courses.embedinvisible AS courses_embedinvisible, \
+courses.description AS courses_description, \
+courses.internal AS courses_internal
+""",
+		None if is_mod else 'WHERE "courses"."visible" AND "courses"."listed" AND "lectures"."visible"',
+		'"lectures"."time" DESC',
+		30,
+		search_term
+	)
+
+
+def _query_search(
+		table: str,
+		search_columns: list[str],
+		join_clause: str or None,
+		extra_select_columns: str or None,
+		where_clause: str or None,
+		extra_ordering: str or None,
+		limit: int,
+		search_term: str):
+	base_sub_query = f"""
+			SELECT "{table}"."id" AS "_id", CAST(%s AS INT) AS "_priority" FROM "{table}" WHERE {" OR ".join(
+		map(lambda column: f'LOWER("{table}"."{column}") LIKE ?',
+			search_columns))}
+		"""
+	
+	words: list[str] = list(filter(lambda w: not w.isspace(), search_term.split(" ")))
+	if len(words) == 0:
+		return []
+	sub_queries: list[str] = []
+	all_values: list[DbValueType] = []
+	prio = len(words)
+	for word in words:
+		word = word.lower()
+		word = word.replace("%", "\\%").replace("_", "\\_")
+		word = "%" + word + "%"
+		sub_queries.append(base_sub_query % prio)
+		for _ in range(0, len(search_columns)):
+			all_values.append(word)
+		prio -= 1
+	
+	return query(f"""
+		SELECT "{table}".* {"" if extra_select_columns is None else "," + extra_select_columns}
+		FROM "{table}"
+		JOIN (
+			SELECT "_id", CAST(SUM("_priority") AS INT) AS "_score"
+			FROM ({"UNION ALL".join(sub_queries)}) AS "_sub_result"
+			GROUP BY "_id"
+		) AS "_data" ON ("{table}"."id" = "_data"."_id")
+		{"" if join_clause is None else join_clause}
+		{"" if where_clause is None else where_clause}
+		ORDER BY "_data"."_score" DESC{"" if extra_ordering is None else ", " + extra_ordering} LIMIT {limit}
+		""", *all_values)
+
+
 def check_mod(user, groups):
 	if not user:
 		return False