server.py 20 KB
Newer Older
1
from flask import Flask, g, request, url_for, redirect, session, render_template, flash, Response, make_response
2
from werkzeug.routing import Rule
3
from functools import wraps
Julian Rother's avatar
Julian Rother committed
4
from datetime import date, timedelta, datetime, time, MINYEAR
5
import os
6
import sys
Julian Rother's avatar
Julian Rother committed
7
import hashlib
8
import random
9
import traceback
10
import string
11
from socket import gethostname
Julian Rother's avatar
Julian Rother committed
12
from ipaddress import ip_address, ip_network
13
import math
Julian Rother's avatar
Julian Rother committed
14
import locale
15
import base64
16
import json
Julian Rother's avatar
Julian Rother committed
17
18

locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
19

20
app = Flask(__name__)
21

22
config = app.config
23
config.from_pyfile('config.py.example', silent=True)
24
25
26
if sys.argv[0].endswith('run.py'): 
	config['SQLITE_INIT_DATA'] = True
	config['DEBUG'] = True
27
config.from_pyfile('config.py', silent=True)
Andreas Valder's avatar
Andreas Valder committed
28
29
30
31
32
33
34
35
36
37
if sys.argv[0].endswith('tests.py'):
	print('running in test mode')
	import tempfile
	# ensure we always use a clean sqlite db for tests
	config['DB_ENGINE'] = 'sqlite'
	config['SQLITE_DB'] = tempfile.mktemp(prefix='flasktestingtmp')
	print('DB File: {}'.format(config['SQLITE_DB']))
	config['SQLITE_INIT_DATA'] = True
	config['SQLITE_INIT_SCHEMA'] = True
	config['DEBUG'] = True
Andreas Valder's avatar
Andreas Valder committed
38
39
if config['DEBUG']:
	app.jinja_env.auto_reload = True
Andreas Valder's avatar
Andreas Valder committed
40

41
42
if not config.get('SECRET_KEY', None):
	config['SECRET_KEY'] = os.urandom(24)
Julian Rother's avatar
Julian Rother committed
43

44
45
from db import query, modify, show, searchquery
from ldap import ldapauth
46
from legacy import legacy_index
47
from scheduler import sched_func
Julian Rother's avatar
Julian Rother committed
48

49
mod_endpoints = []
Julian Rother's avatar
Julian Rother committed
50

51
def mod_required(func):
52
	mod_endpoints.append(func.__name__)
53
54
	@wraps(func)
	def decorator(*args, **kwargs):
55
		if not ismod():
56
57
58
59
60
61
			flash('Diese Funktion ist nur für Moderatoren verfügbar!')
			return redirect(url_for('login', ref=request.url))
		else:
			return func(*args, **kwargs)
	return decorator

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def evalperm(perms):
	cperms = []
	lperms = []
	vperms = []
	for perm in perms:
		if perm['course_id']:
			cperms.append(perm)
		elif perm['lecture_id']:
			lperms.append(perm)
		elif perm['video_id']:
			vperms.append(perm)
	if vperms:
		return vperms
	elif lperms:
	 	return lperms
	elif cperms:
		return cperms
	return [{'type': 'public'}]
80

81
from template_helper import *
82

83
app.jinja_env.globals['navbar'] = []
84
85
86
87
88
# iconlib can be 'bootstrap'
# ( see: http://getbootstrap.com/components/#glyphicons )
# or 'fa'
# ( see: http://fontawesome.io/icons/ )
def register_navbar(name, iconlib='bootstrap', icon=None):
89
	def wrapper(func):
90
		endpoint = func.__name__
91
		app.jinja_env.globals['navbar'].append((endpoint, name, iconlib, icon, not endpoint in mod_endpoints))
92
93
94
		return func
	return wrapper

Julian Rother's avatar
Cleanup    
Julian Rother committed
95
96
97
98
def render_endpoint(endpoint, flashtext=None, **kargs):
	if flashtext:
		flash(flashtext)
	# request.endpoint is used for navbar highlighting
99
	request.url_rule = Rule(request.path, endpoint=endpoint)
Julian Rother's avatar
Cleanup    
Julian Rother committed
100
101
	return app.view_functions[endpoint](**kargs)

102
103
104
105
106
107
108
def handle_errors(endpoint, text, code, *errors, **epargs):
	def wrapper(func):
		@wraps(func)
		def decorator(*args, **kwargs):
			try:
				return func(*args, **kwargs)
			except errors:
Julian Rother's avatar
Julian Rother committed
109
				if endpoint:
110
					return make_response(render_endpoint(endpoint, text, **epargs), code)
Julian Rother's avatar
Julian Rother committed
111
112
				else:
					return text, code
113
114
115
		return decorator
	return wrapper

Julian Rother's avatar
Cleanup    
Julian Rother committed
116
@app.errorhandler(404)
117
@app.route('/invalidpath')
Julian Rother's avatar
Julian Rother committed
118
def handle_not_found(e=None):
119
	return render_endpoint('index', 'Diese Seite existiert nicht!'), 404
Julian Rother's avatar
Cleanup    
Julian Rother committed
120

121
122
123
124
@app.errorhandler(500)
@app.errorhandler(Exception)
def handle_internal_error(e):
	traceback.print_exc()
125
	return render_template('500.html'), 500
126

127
128
129
130
131
132
133
134
135
136
@sched_func(5*60, firstdelay=0)
def dump_error_page():
	if 'ERROR_PAGE' not in config:
		return
	request.url_rule = Rule(request.path, endpoint='handle_internal_error')
	text = render_template('500.html')
	f = open(config['ERROR_PAGE'], 'w')
	f.write(text)
	f.close()

137
138
139
140
141
142
143
144
145
def genlive(streams):
        for stream in streams:
                stream['visible'] = True
                stream['downloadable'] = False
                stream['path'] = 'pub/hls/%s.m3u8'%stream['livehandle']
                stream['file_size'] = 0
        return streams


146
@app.route('/')
147
@register_navbar('Home', icon='home')
148
def index():
149
	# handle legacy urls...
150
151
152
	result = legacy_index()
	if result:
		return result
153

154
	start = date.today()
155
	end = start + timedelta(days=7)
156
	upcomming = query('''
157
		SELECT lectures.*, streams.active AS nowlive, "course" AS sep, courses.*
Andreas Valder's avatar
Andreas Valder committed
158
159
		FROM lectures
		JOIN courses ON (lectures.course_id = courses.id)
160
		LEFT JOIN streams ON lectures.id = streams.lecture_id
161
162
		WHERE (time > ?) AND (time < ?) AND (? OR (lectures.visible AND courses.visible AND courses.listed)) AND NOT lectures.norecording
		ORDER BY time ASC LIMIT 30''', start, end, ismod())
Andreas Valder's avatar
Andreas Valder committed
163
164
165
	for i in upcomming:
		i['date'] = i['time'].date()
	latestvideos=query('''
166
		SELECT lectures.*, "course" AS sep, courses.*
Andreas Valder's avatar
Andreas Valder committed
167
168
169
170
171
		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
172
		ORDER BY MAX(videos.time_created) DESC
Andreas Valder's avatar
Andreas Valder committed
173
		LIMIT 6	''',ismod())
174
	livestreams = query('''SELECT streams.handle AS livehandle, lectures.*, "course" AS sep, courses.*
175
176
177
178
179
		FROM streams
		JOIN lectures ON lectures.id = streams.lecture_id
		JOIN courses ON courses.id = lectures.course_id
		WHERE streams.active AND (? OR (streams.visible AND courses.visible AND courses.listed AND lectures.visible))
		''', ismod())
Julian Rother's avatar
Julian Rother committed
180
	featured = query('SELECT * FROM featured WHERE (? OR visible) ORDER BY `order`', ismod())
181
	featured = list(filter(lambda x: not x['deleted'], featured))
Julian Rother's avatar
Julian Rother committed
182
183
184
185
	for item in featured:
		if item['type'] == 'courses':
			if item['param'] not in ['title', 'semester', 'organizer', 'subject']:
				continue
186
			item['courses'] = query('SELECT * FROM courses WHERE (visible AND listed) AND `%s` = ? ORDER BY `%s`'%(item['param'], item['param']), item['param2'])
Julian Rother's avatar
Julian Rother committed
187
188
		elif item['type'] == 'video':
			item['lecture'] = {'id': item['param']}
189
			streams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
Julian Rother's avatar
Julian Rother committed
190
191
192
193
194
195
196
197
198
199
200
					FROM streams
					JOIN lectures ON lectures.id = streams.lecture_id
					JOIN formats ON formats.keywords = "hls"
					WHERE streams.active AND streams.visible AND lectures.id = ?
					''', item['param'])
			item['videos'] = query('''
					SELECT videos.*, "formats" AS sep, formats.*
					FROM videos
					JOIN formats ON (videos.video_format = formats.id)
					WHERE videos.lecture_id = ? AND videos.visible
					ORDER BY formats.prio DESC
201
					''', item['param'])+genlive(streams)
202
	return render_template('index.html', latestvideos=livestreams+latestvideos, upcomming=upcomming, featured=featured)
203

204
@app.route('/courses')
205
@register_navbar('Videos', icon='film')
206
def courses():
207
	courses = query('SELECT * FROM courses WHERE (? OR (visible AND listed)) ORDER BY lower(semester), lower(title)', ismod())
208
209
210
	for course in courses:
		if course['semester'] == '':
			course['semester'] = 'zeitlos'
Andreas Valder's avatar
Andreas Valder committed
211
	groupedby = request.args.get('groupedby')
Julian Rother's avatar
Julian Rother committed
212
	if groupedby not in ['title', 'semester', 'organizer', 'subject']:
Andreas Valder's avatar
Andreas Valder committed
213
		groupedby = 'semester'
214
	return render_template('courses.html', courses=courses, groupedby=groupedby)
Andreas Valder's avatar
Andreas Valder committed
215

216
217
@app.route('/<handle>')
@app.route('/<int:id>')
218
@handle_errors('courses', 'Diese Veranstaltung existiert nicht!', 404, IndexError)
219
220
def course(id=None, handle=None):
	if id:
221
		course = query('SELECT * FROM courses WHERE id = ? AND (? OR visible)', id, ismod())[0]
222
	else:
223
		course = query('SELECT * FROM courses WHERE handle = ? AND (? OR visible)', handle, ismod())[0]
224
225
	course['perm'] = query('SELECT * FROM perm WHERE (NOT perm.deleted) AND course_id = ? ORDER BY type', course['id'])
	perms = query('SELECT perm.* FROM perm JOIN lectures ON (perm.lecture_id = lectures.id) WHERE (NOT perm.deleted) AND lectures.course_id = ? ORDER BY perm.type', course['id'])
226
	lectures = query('SELECT * FROM lectures WHERE course_id = ? AND (? OR visible) ORDER BY time, duration DESC', course['id'], ismod())
227
	for lecture in lectures:
228
		lecture['perm'] = []
229
		lecture['perm'] += course['perm']
230
		lecture['course'] = course
231
232
233
		for perm in perms:
			if perm['lecture_id'] == lecture['id']:
				lecture['perm'].append(perm)
Andreas Valder's avatar
Andreas Valder committed
234
	videos = query('''
235
			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
Andreas Valder's avatar
Andreas Valder committed
236
237
238
239
240
241
			FROM videos
			JOIN lectures ON (videos.lecture_id = lectures.id)
			JOIN formats ON (videos.video_format = formats.id)
			JOIN courses ON (lectures.course_id = courses.id)
			WHERE lectures.course_id= ? AND (? OR videos.visible)
			ORDER BY lectures.time, formats.prio DESC
242
			''', course['id'], ismod())
243
	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
244
245
246
247
248
			FROM streams
			JOIN lectures ON lectures.id = streams.lecture_id
			JOIN formats ON formats.keywords = "hls"
			WHERE streams.active AND (? OR streams.visible) AND lectures.course_id = ?
			''', ismod(), course['id'])
249
250
251
	chapters = []
	if course['coursechapters']:
		chapters = query('SELECT chapters.* FROM chapters JOIN lectures ON lectures.id = chapters.lecture_id WHERE lectures.course_id = ? AND NOT chapters.deleted AND chapters.visible ORDER BY time ASC', course['id'])
252
	videos += genlive(livestreams)
253
	return render_template('course.html', course=course, lectures=lectures, videos=videos, chapters=chapters)
Andreas Valder's avatar
Andreas Valder committed
254

Andreas Valder's avatar
Andreas Valder committed
255
@app.route('/faq')
256
@register_navbar('FAQ', icon='question-sign')
Andreas Valder's avatar
Andreas Valder committed
257
def faq():
258
	return render_template('faq.html')
Andreas Valder's avatar
Andreas Valder committed
259

260
261
262
263
@app.route('/<course>/<int:id>')
@app.route('/<int:courseid>/<int:id>')
@app.route('/<course>/<int:id>/embed', endpoint='embed')
@app.route('/<int:courseid>/<int:id>/embed', endpoint='embed')
264
@handle_errors('course', 'Diese Vorlesung existiert nicht!', 404, IndexError)
265
def lecture(id, course=None, courseid=None):
Andreas Valder's avatar
Andreas Valder committed
266
267
	lecture = query('SELECT * FROM lectures WHERE id = ? AND (? OR visible)', id, ismod())[0]
	videos = query('''
268
			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
Andreas Valder's avatar
Andreas Valder committed
269
270
			FROM videos
			JOIN formats ON (videos.video_format = formats.id)
271
272
273
274
			JOIN courses ON (courses.id = ?)
			WHERE videos.lecture_id = ? AND (? OR videos.visible)
			ORDER BY formats.prio DESC
			''', lecture['course_id'], lecture['id'], ismod())
275
	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
276
277
278
279
280
281
			FROM streams
			JOIN lectures ON lectures.id = streams.lecture_id
			JOIN formats ON formats.keywords = "hls"
			WHERE streams.active AND (? OR streams.visible) AND lectures.id = ?
			''', ismod(), id)
	videos += genlive(livestreams)
282
	perms = query('SELECT perm.* FROM perm WHERE ((NOT perm.deleted) AND (perm.lecture_id = ? OR perm.course_id = ?))',
283
			lecture['id'], lecture['course_id'])
284
	if not videos:
285
286
287
288
289
290
		if lecture['live'] and lecture['time'] > datetime.now()-timedelta(minutes=30) and lecture['time']-timedelta(hours=20) < datetime.now():
			flash('Der Livestream beginnt um '+human_time(lecture['time'])+' Uhr.')
		elif lecture['time'] > datetime.now():
			flash('Diese Vorlesung hat noch nicht stattgefunden!')
		else:
			flash('Zu dieser Vorlesung wurden noch keine Videos veröffentlicht!')
291
	courses = query('SELECT * FROM courses WHERE id = ? AND (? OR visible)', lecture['course_id'], ismod())
292
293
	if not courses:
		return render_endpoint('courses', 'Diese Veranstaltung existiert nicht!'), 404
294
	chapters = query('SELECT * FROM chapters WHERE lecture_id = ? AND NOT deleted AND (? OR visible) ORDER BY time ASC', id, ismod())
295
296
297
298
299
	username = password = None
	if request.authorization:
		username = request.authorization.username
		password = request.authorization.password
	if not checkperm(perms, username=username, password=password):
300
		mode, text = permdescr(perms)
301
		if mode == 'rwth':
302
			flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_rwthauth')+'">Hier authorisieren</a>.', category='player')
303
		elif mode == 'l2p':
304
			if 'l2p_courses' in session:
305
				flash(text+'. Du bist kein Teilnehmer des L2P-Kurses! <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Kurse aktualisieren</a>.', category='player')
306
			else:
307
				flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Hier authorisieren</a>.', category='player')
308
		else:
309
			flash(text+'.', category='player')
310
	return render_template('embed.html' if request.endpoint == 'embed' else 'lecture.html', course=courses[0], lecture=lecture, videos=videos, chapters=chapters, seek=request.args.get('t'))
Andreas Valder's avatar
Andreas Valder committed
311

312
313
314
315
316
317
318

@app.route('/search')
def search():
	if 'q' not in request.args:
		return redirect(url_for('index'))
	q = request.args['q']
	courses = searchquery(q, '*', ['title', 'short', 'organizer', 'subject', 'description'],
319
			'courses', 'WHERE (? OR (visible AND listed)) GROUP BY id ORDER BY _score DESC, semester DESC LIMIT 20', ismod())
Julian Rother's avatar
Julian Rother committed
320
321
322
323
324
	#lectures = searchquery(q, 'lectures.*, courses.visible AS coursevisible, courses.listed, "course" AS sep, courses.*',
	#			['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())
	lectures = searchquery(q, '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, courses.responsible AS courses_responsible',
325
326
			['lectures.title', 'lectures.comment', 'lectures.speaker', 'courses.short'],
			'lectures LEFT JOIN courses on (courses.id = lectures.course_id)',
327
			'WHERE (? OR (coursevisible AND listed AND visible)) GROUP BY id ORDER BY _score DESC, time DESC LIMIT 30', ismod())
Julian Rother's avatar
Julian Rother committed
328
329
330
331
332
	for lecture in lectures:
		lecture['course'] = {}
		for key in lecture:
			if key.startswith('courses_'):
				lecture['course'][key[8:]] = lecture[key]
333
	return render_template('search.html', searchtext=request.args['q'], courses=courses, lectures=lectures)
Andreas Valder's avatar
Andreas Valder committed
334

335
def check_mod(user, groups):
336
337
338
339
340
341
	if not user:
		return False
	for group in config['LDAP_GROUPS']:
		if group in groups:
			return True
	return False
342

343
@app.route('/internal/login', methods=['GET', 'POST'])
Julian Rother's avatar
Julian Rother committed
344
def login():
345
346
	if request.method == 'GET':
		return render_template('login.html')
347
348
	userinfo, groups = ldapauth(request.form.get('user'), request.form.get('password'))
	user = userinfo.get('uid')
349
	if not check_mod(user, groups):
350
		flash('Login fehlgeschlagen!')
Andreas Valder's avatar
Andreas Valder committed
351
		return make_response(render_template('login.html'), 403)
352
	session['user'] = userinfo
353
354
	dbuser = query('SELECT * FROM users WHERE name = ?', user)
	if not dbuser:
Julian Rother's avatar
Julian Rother committed
355
		modify('INSERT INTO users (name, realname, fsacc, level, calendar_key, rfc6238) VALUES (?, ?, ?, 1, "", "")', user, session['user']['givenName'], user)
356
357
		dbuser = query('SELECT * FROM users WHERE name = ?', user)
	session['user']['dbid'] = dbuser[0]['id']
358
	session['_csrf_token'] = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(64))
Andreas Valder's avatar
Andreas Valder committed
359
	session.permanent = True
Julian Rother's avatar
Julian Rother committed
360
	return redirect(request.values.get('ref', url_for('index')))
Julian Rother's avatar
Julian Rother committed
361

362
@app.route('/internal/logout', methods=['GET', 'POST'])
Julian Rother's avatar
Julian Rother committed
363
def logout():
364
	session.pop('user', None)
Julian Rother's avatar
Julian Rother committed
365
	return redirect(request.values.get('ref', url_for('index')))
Julian Rother's avatar
Julian Rother committed
366

367
@app.route('/internal/auth')
368
369
370
371
def auth(): # For use with nginx auth_request
	if 'X-Original-Uri' not in request.headers:
		return 'Internal Server Error', 500
	url = request.headers['X-Original-Uri'].lstrip(config['VIDEOPREFIX'])
Julian Rother's avatar
Julian Rother committed
372
373
	if request.cookies.get('tracking', '') and request.cookies['tracking'].isdigit():
		cookie = int(request.cookies['tracking'])
374
	else:
Julian Rother's avatar
Julian Rother committed
375
		cookie = random.getrandbits(8*8-1)
376
	if url.endswith('jpg') or ismod():
377
		return "OK", 200
378
379
	if url.startswith('pub/hls/'):
		handle = url[len('pub/hls/'):].split('_')[0].split('.')[0]
380
		perms = query('''SELECT lectures.id AS lecture, perm.*
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
				FROM streams
				JOIN lectures ON (streams.lecture_id = lectures.id)
				JOIN courses ON (lectures.course_id = courses.id)
				LEFT JOIN perm ON ((lectures.id = perm.lecture_id OR courses.id = perm.course_id) AND NOT perm.deleted)
				WHERE streams.handle = ?
				AND (courses.visible AND lectures.visible AND streams.visible)
				ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''', handle)
	else:
		perms = query('''SELECT videos.path, videos.id AS vid, perm.*
				FROM videos
				JOIN lectures ON (videos.lecture_id = lectures.id)
				JOIN courses ON (lectures.course_id = courses.id)
				LEFT JOIN perm ON ((videos.id = perm.video_id OR lectures.id = perm.lecture_id OR courses.id = perm.course_id) AND NOT perm.deleted)
				WHERE videos.path = ?
				AND (courses.visible AND lectures.visible AND videos.visible)
				ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''',
				url)
398
	if not perms:
399
		return "Not found", 404
400
	auth = request.authorization
401
402
403
404
	username = password = None
	if auth:
		username = auth.username
		password = auth.password
405
	if checkperm(perms, username=username, password=password):
406
		try:
407
408
			if not url.startswith('pub/hls/'):
				modify('INSERT INTO log (id, `time`, `date`, video, source) VALUES (?, ?, ?, ?, 1)', cookie, datetime.now(), datetime.combine(date.today(), time()), perms[0]['vid'])
409
410
411
412
			elif url.endswith('.ts'):
				fmt = url.split('_')[-1].split('-')[0]
				seg = url.split('.')[0].split('-')[-1]
				modify('INSERT INTO hlslog (id, `time`, segment, lecture, handle, format) VALUES (?, ?, ?, ?, ?, ?)', cookie, datetime.now(), seg, perms[0]['lecture'], handle, fmt)
413
414
415
		except:
			pass
		r = make_response('OK', 200)
Julian Rother's avatar
Julian Rother committed
416
		r.set_cookie('tracking', str(cookie), max_age=2147483647) # Many many years
417
		return r
418
	password_auth = False
419
420
	for perm in perms:
		if perm['type'] == 'password':
421
422
423
			password_auth = True
			break
	if password_auth:
424
425
		return Response("Login required", 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
	return "Not allowed", 403
Andreas Valder's avatar
Andreas Valder committed
426

Julian Rother's avatar
Julian Rother committed
427
428
429
430
@app.route('/files/<filename>')
def files(filename):
	return redirect(config['VIDEOPREFIX']+'/'+filename)

Andreas Valder's avatar
Andreas Valder committed
431
@app.route('/sitemap.xml')
Andreas Valder's avatar
Andreas Valder committed
432
433
434
435
436
437
438
439
440
441
def sitemap():
	pages=[]
	# static pages
	for rule in app.url_map.iter_rules():
		if 'GET' in rule.methods and len(rule.arguments)==0:
			if rule.endpoint not in mod_endpoints:
				pages.append([rule.rule])
	for i in query('select * from courses where visible and listed'):
		pages.append([url_for('course',handle=i['handle'])])
		for j in query('select * from lectures where (course_id = ? and visible)',i['id']):
442
			pages.append([url_for('lecture',course=i['handle'],id=j['id'])])
Andreas Valder's avatar
Andreas Valder committed
443
444
445
446


	return Response(render_template('sitemap.xml', pages=pages), 200, {'Content-Type': 'application/atom+xml'} )

Julian Rother's avatar
Julian Rother committed
447
448
449
450
451
452
453
454
455
@app.route('/internal/dbstatus')
@register_navbar('DB-Status', icon='ok')
@mod_required
def dbstatus():
	hosts = set()
	clusters = {}
	status = {}
	variables = {}
	for host in config.get('MYSQL_DBSTATUS_HOSTS', [])+[config.get('MYSQL_HOST', None)]:
Andreas Valder's avatar
Andreas Valder committed
456
457
458
459
460
		try:
			for _host in show('SHOW VARIABLES LIKE "wsrep_cluster_address"', host=host)['wsrep_cluster_address'][len('gcomm://'):].split(','):
				hosts.add(_host)
		except:
			pass
Julian Rother's avatar
Julian Rother committed
461
	for host in sorted(list(hosts)):
Julian Rother's avatar
Julian Rother committed
462
463
464
465
466
467
468
469
470
471
472
473
		try:
			status[host] = show('SHOW GLOBAL STATUS LIKE "wsrep%"', host=host)
			variables[host] = show('SHOW GLOBAL VARIABLES LIKE "wsrep%"', host=host)
		except:
			status[host] = {'wsrep_cluster_state_uuid': '', 'wsrep_local_state_comment': 'Not reachable', 'wsrep_cluster_conf_id': '0', 'wsrep_cluster_status': 'Unknown'}
			variables[host] = {'wsrep_node_name': host, 'wsrep_cluster_name': 'unknown'}
		cluster = variables[host]['wsrep_cluster_name']+'-'+status[host]['wsrep_cluster_conf_id']
		if cluster not in clusters:
			clusters[cluster] = []
		clusters[cluster].append(host)
	return render_template('dbstatus.html', clusters=clusters, statuses=status, vars=variables), 200

Andreas Valder's avatar
Andreas Valder committed
474
import edit
Julian Rother's avatar
Julian Rother committed
475
import feeds
476
import importer
477
import stats
Andreas Valder's avatar
Andreas Valder committed
478
import sorter
479
480
if 'ICAL_URL' in config:
	import meetings
481
import l2pauth
Andreas Valder's avatar
Andreas Valder committed
482
483
if 'JOBS_API_KEY' in config:
	import jobs
Andreas Valder's avatar
Andreas Valder committed
484
import timetable
Andreas Valder's avatar
Andreas Valder committed
485
import chapters
486
import icalexport
487
import livestreams