server.py 20.8 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
38
	config['DISABLE_SCHEDULER'] = True
Andreas Valder's avatar
Andreas Valder committed
39
40
if config['DEBUG']:
	app.jinja_env.auto_reload = True
Andreas Valder's avatar
Andreas Valder committed
41

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

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

51
mod_endpoints = []
Julian Rother's avatar
Julian Rother committed
52

53
def mod_required(func):
54
	mod_endpoints.append(func.__name__)
55
56
	@wraps(func)
	def decorator(*args, **kwargs):
57
		if not ismod():
58
59
60
61
62
63
			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

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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'}]
82

83
from template_helper import *
84

85
app.jinja_env.globals['navbar'] = []
86
87
88
89
90
# 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):
91
	def wrapper(func):
92
		endpoint = func.__name__
93
		app.jinja_env.globals['navbar'].append((endpoint, name, iconlib, icon, not endpoint in mod_endpoints))
94
95
96
		return func
	return wrapper

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

104
105
106
107
108
109
110
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
111
				if endpoint:
112
					return make_response(render_endpoint(endpoint, text, **epargs), code)
Julian Rother's avatar
Julian Rother committed
113
114
				else:
					return text, code
115
116
117
		return decorator
	return wrapper

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

123
124
125
126
@app.errorhandler(500)
@app.errorhandler(Exception)
def handle_internal_error(e):
	traceback.print_exc()
127
	notify_admins('endpoint_exception', traceback=traceback.format_exc())
128
	return render_template('500.html', online=True), 500
129

130
131
132
133
134
135
136
137
138
139
@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()

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


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

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

207
@app.route('/courses')
208
@register_navbar('Videos', icon='film')
209
def courses():
210
	courses = query('SELECT * FROM courses WHERE (? OR (visible AND listed)) ORDER BY lower(semester), lower(title)', ismod())
211
	chapters = {}
212
	for i in query('SELECT lectures.course_id AS id, COUNT(chapters.id) AS c FROM chapters JOIN lectures ON chapters.lecture_id = lectures.id WHERE NOT chapters.visible AND NOT chapters.deleted GROUP BY lectures.course_id'):
213
		chapters[i['id']] = i['c']
214
	for course in courses:
215
		course['chapter_count'] = chapters.get(course['id'], 0)
216
217
		if course['semester'] == '':
			course['semester'] = 'zeitlos'
Andreas Valder's avatar
Andreas Valder committed
218
	groupedby = request.args.get('groupedby')
Julian Rother's avatar
Julian Rother committed
219
	if groupedby not in ['title', 'semester', 'organizer', 'subject']:
Andreas Valder's avatar
Andreas Valder committed
220
		groupedby = 'semester'
221
	return render_template('courses.html', courses=courses, groupedby=groupedby)
Andreas Valder's avatar
Andreas Valder committed
222

223
224
@app.route('/<handle>')
@app.route('/<int:id>')
225
@handle_errors('courses', 'Diese Veranstaltung existiert nicht!', 404, IndexError)
226
227
def course(id=None, handle=None):
	if id:
228
		course = query('SELECT * FROM courses WHERE id = ? AND (? OR visible)', id, ismod())[0]
229
	else:
230
		course = query('SELECT * FROM courses WHERE handle = ? AND (? OR visible)', handle, ismod())[0]
231
232
	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'])
233
	chapters = {}
Andreas Valder's avatar
Andreas Valder committed
234
	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']):
235
		chapters[i['id']] = i['c']
236
	lectures = query('SELECT * FROM lectures WHERE course_id = ? AND (? OR visible) ORDER BY time, duration DESC', course['id'], ismod())
237
	for lecture in lectures:
238
		lecture['perm'] = []
239
		lecture['perm'] += course['perm']
240
		lecture['course'] = course
241
		lecture['chapter_count'] = chapters.get(lecture['id'], 0)
242
243
244
		for perm in perms:
			if perm['lecture_id'] == lecture['id']:
				lecture['perm'].append(perm)
Andreas Valder's avatar
Andreas Valder committed
245
	videos = query('''
246
			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
Andreas Valder's avatar
Andreas Valder committed
247
248
249
250
251
252
			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
253
			''', course['id'], ismod())
254
	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
255
256
257
258
259
			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'])
260
261
262
	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'])
263
	videos += genlive(livestreams)
264
265
266
267
268
269
	responsible = query('''SELECT users.*, responsible.course_id AS responsible
			FROM users
			LEFT JOIN responsible ON (responsible.user_id = users.id AND responsible.course_id = ?)
			WHERE users.fsacc != "" AND users.level > 0
			ORDER BY responsible DESC, users.realname ASC''', course['id'])
	return render_template('course.html', course=course, lectures=lectures, videos=videos, chapters=chapters, responsible=responsible)
Andreas Valder's avatar
Andreas Valder committed
270

Andreas Valder's avatar
Andreas Valder committed
271
@app.route('/faq')
272
@register_navbar('FAQ', icon='question-sign')
Andreas Valder's avatar
Andreas Valder committed
273
def faq():
274
	return render_template('faq.html')
Andreas Valder's avatar
Andreas Valder committed
275

276
277
278
279
@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')
280
@handle_errors('course', 'Diese Vorlesung existiert nicht!', 404, IndexError)
281
def lecture(id, course=None, courseid=None):
Andreas Valder's avatar
Andreas Valder committed
282
283
	lecture = query('SELECT * FROM lectures WHERE id = ? AND (? OR visible)', id, ismod())[0]
	videos = query('''
284
			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
Andreas Valder's avatar
Andreas Valder committed
285
286
			FROM videos
			JOIN formats ON (videos.video_format = formats.id)
287
288
289
290
			JOIN courses ON (courses.id = ?)
			WHERE videos.lecture_id = ? AND (? OR videos.visible)
			ORDER BY formats.prio DESC
			''', lecture['course_id'], lecture['id'], ismod())
291
	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
292
293
294
295
296
297
			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)
298
	perms = query('SELECT perm.* FROM perm WHERE ((NOT perm.deleted) AND (perm.lecture_id = ? OR perm.course_id = ?))',
299
			lecture['id'], lecture['course_id'])
300
	if not videos:
301
302
303
304
305
306
		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!')
307
	courses = query('SELECT * FROM courses WHERE id = ? AND (? OR visible)', lecture['course_id'], ismod())
308
309
	if not courses:
		return render_endpoint('courses', 'Diese Veranstaltung existiert nicht!'), 404
310
	chapters = query('SELECT * FROM chapters WHERE lecture_id = ? AND NOT deleted AND (? OR visible) ORDER BY time ASC', id, ismod())
311
312
313
314
315
	username = password = None
	if request.authorization:
		username = request.authorization.username
		password = request.authorization.password
	if not checkperm(perms, username=username, password=password):
316
		mode, text = permdescr(perms)
317
		if mode == 'rwth':
318
			flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_rwthauth')+'">Hier authorisieren</a>.', category='player')
319
		elif mode == 'l2p':
320
			if 'l2p_courses' in session:
321
				flash(text+'. Du bist kein Teilnehmer des L2P-Kurses! <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Kurse aktualisieren</a>.', category='player')
322
			else:
323
				flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Hier authorisieren</a>.', category='player')
324
		else:
325
			flash(text+'.', category='player')
326
	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
327

328
329
330
331
332
333
334

@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'],
335
			'courses', 'WHERE (? OR (visible AND listed)) GROUP BY id ORDER BY _score DESC, semester DESC LIMIT 20', ismod())
336
	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',
337
338
			['lectures.title', 'lectures.comment', 'lectures.speaker', 'courses.short'],
			'lectures LEFT JOIN courses on (courses.id = lectures.course_id)',
339
			'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
340
341
342
343
344
	for lecture in lectures:
		lecture['course'] = {}
		for key in lecture:
			if key.startswith('courses_'):
				lecture['course'][key[8:]] = lecture[key]
345
	return render_template('search.html', searchtext=request.args['q'], courses=courses, lectures=lectures)
Andreas Valder's avatar
Andreas Valder committed
346

347
def check_mod(user, groups):
348
349
350
351
352
353
	if not user:
		return False
	for group in config['LDAP_GROUPS']:
		if group in groups:
			return True
	return False
354

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

374
@app.route('/internal/logout', methods=['GET', 'POST'])
Julian Rother's avatar
Julian Rother committed
375
def logout():
376
	session.pop('user', None)
Julian Rother's avatar
Julian Rother committed
377
	return redirect(request.values.get('ref', url_for('index')))
Julian Rother's avatar
Julian Rother committed
378

379
@app.route('/internal/auth')
380
381
382
383
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
384
385
	if request.cookies.get('tracking', '') and request.cookies['tracking'].isdigit():
		cookie = int(request.cookies['tracking'])
386
	else:
Julian Rother's avatar
Julian Rother committed
387
		cookie = random.getrandbits(8*8-1)
388
	if url.endswith('jpg') or ismod():
389
		return "OK", 200
390
391
	if url.startswith('pub/hls/'):
		handle = url[len('pub/hls/'):].split('_')[0].split('.')[0]
392
		perms = query('''SELECT lectures.id AS lecture, perm.*
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
				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)
410
	if not perms:
411
		return "Not found", 404
412
	auth = request.authorization
413
414
415
416
	username = password = None
	if auth:
		username = auth.username
		password = auth.password
417
	if checkperm(perms, username=username, password=password):
418
		try:
419
420
			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'])
421
422
423
424
			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)
425
426
427
		except:
			pass
		r = make_response('OK', 200)
Julian Rother's avatar
Julian Rother committed
428
		r.set_cookie('tracking', str(cookie), max_age=2147483647) # Many many years
429
		return r
430
	password_auth = False
431
432
	for perm in perms:
		if perm['type'] == 'password':
433
434
435
			password_auth = True
			break
	if password_auth:
436
437
		return Response("Login required", 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
	return "Not allowed", 403
Andreas Valder's avatar
Andreas Valder committed
438

Julian Rother's avatar
Julian Rother committed
439
440
441
442
@app.route('/files/<filename>')
def files(filename):
	return redirect(config['VIDEOPREFIX']+'/'+filename)

Andreas Valder's avatar
Andreas Valder committed
443
@app.route('/sitemap.xml')
Andreas Valder's avatar
Andreas Valder committed
444
445
446
447
448
449
450
451
452
453
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']):
454
			pages.append([url_for('lecture',course=i['handle'],id=j['id'])])
Andreas Valder's avatar
Andreas Valder committed
455
456
457
458


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

Julian Rother's avatar
Julian Rother committed
459
460
461
462
463
464
465
466
467
@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
468
469
470
471
472
		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
473
	for host in sorted(list(hosts)):
Julian Rother's avatar
Julian Rother committed
474
475
476
477
478
479
480
481
482
483
484
485
		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

486
487
488
def date_json_handler(obj):
	return obj.isoformat() if hasattr(obj, 'isoformat') else obj

489
490
from jobs import job_handler, schedule_job
from edit import edit_handler
Julian Rother's avatar
Julian Rother committed
491
import feeds
492
import importer
493
import stats
494
495
if 'ICAL_URL' in config:
	import meetings
496
import l2pauth
Julian Rother's avatar
Julian Rother committed
497
import sorter
498
import encoding
Andreas Valder's avatar
Andreas Valder committed
499
import timetable
Andreas Valder's avatar
Andreas Valder committed
500
import chapters
501
import icalexport
502
import livestreams