realms-wiki/realms/__init__.py

325 lines
10 KiB
Python
Raw Normal View History

2013-09-26 16:51:15 +03:00
import logging
2013-09-30 17:34:16 +03:00
import os
2013-10-02 04:50:48 +03:00
import time
2013-10-05 00:42:45 +03:00
from threading import Lock
2013-10-02 07:32:53 +03:00
2013-10-05 00:42:45 +03:00
import rethinkdb as rdb
from flask import Flask, request, render_template, url_for, redirect, flash
from flask.ext.login import LoginManager, login_required
2013-10-10 00:35:06 +03:00
from flask.ext.assets import Environment, Bundle
2013-10-02 07:32:53 +03:00
from recaptcha.client import captcha
2013-10-03 17:58:07 +03:00
from werkzeug.routing import BaseConverter
2013-10-05 00:42:45 +03:00
2013-09-26 16:51:15 +03:00
from session import RedisSessionInterface
import config
from wiki import Wiki
from util import to_canonical, remove_ext, mkdir_safe, gravatar_url
2013-10-04 05:57:19 +03:00
from models import Site, User, CurrentUser
2013-10-05 00:42:45 +03:00
from ratelimit import get_view_rate_limit, ratelimiter
from services import db
2013-10-10 00:35:06 +03:00
# Flask instance container
2013-10-05 00:42:45 +03:00
instances = {}
2013-10-10 00:35:06 +03:00
# Flask extension objects
login_manager = LoginManager()
assets = Environment()
2013-10-05 00:42:45 +03:00
class SubdomainDispatcher(object):
2013-10-10 00:35:06 +03:00
"""
Application factory
"""
2013-10-05 00:42:45 +03:00
def __init__(self, domain, create_app):
self.domain = domain
self.create_app = create_app
self.lock = Lock()
def get_application(self, host):
host = host.split(':')[0]
assert host.endswith(self.domain), 'Configuration error'
subdomain = host[:-len(self.domain)].rstrip('.')
with self.lock:
app = instances.get(subdomain)
if app is None:
app = self.create_app(subdomain)
instances[subdomain] = app
return app
def __call__(self, environ, start_response):
app = self.get_application(environ['HTTP_HOST'])
return app(environ, start_response)
def init_db(dbname):
2013-10-10 00:35:06 +03:00
"""
Assures DB has minimal setup
"""
2013-10-05 00:42:45 +03:00
if not dbname in rdb.db_list().run(db):
print "Creating DB %s" % dbname
rdb.db_create(dbname).run(db)
for tbl in ['sites', 'users', 'pages']:
if not tbl in rdb.table_list().run(db):
rdb.table_create(tbl).run(db)
2013-10-05 00:57:33 +03:00
if not 'name' in rdb.table('sites').index_list().run(db):
2013-10-05 00:54:46 +03:00
rdb.table('sites').index_create('name').run(db)
2013-10-05 00:52:41 +03:00
for i in ['username', 'email']:
if not i in rdb.table('users').index_list().run(db):
2013-10-05 00:54:46 +03:00
rdb.table('users').index_create(i).run(db)
2013-10-05 00:52:41 +03:00
2013-10-05 00:42:45 +03:00
s = Site()
if not s.get_by_name('_'):
s.create(name='_', repo='_')
2013-10-02 07:32:53 +03:00
2013-09-26 16:51:15 +03:00
2013-10-03 17:58:07 +03:00
class RegexConverter(BaseConverter):
2013-10-10 00:35:06 +03:00
"""
Enables Regex matching on endpoints
"""
2013-10-03 17:58:07 +03:00
def __init__(self, url_map, *items):
super(RegexConverter, self).__init__(url_map)
self.regex = items[0]
2013-09-26 16:51:15 +03:00
2013-10-05 00:42:45 +03:00
def redirect_url(referrer=None):
if not referrer:
referrer = request.referrer
return request.args.get('next') or referrer or url_for('index')
2013-10-04 05:57:19 +03:00
2013-09-26 16:51:15 +03:00
2013-10-04 05:57:19 +03:00
def validate_captcha():
response = captcha.submit(
request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'],
config.flask['RECAPTCHA_PRIVATE_KEY'],
request.remote_addr)
return response.is_valid
2013-09-26 16:51:15 +03:00
2013-10-10 00:35:06 +03:00
def format_subdomain(s):
s = s.lower()
s = to_canonical(s)
if s in ['www', 'api']:
2013-10-10 00:35:06 +03:00
# Not allowed
s = ""
return s
2013-10-05 00:42:45 +03:00
def make_app(subdomain):
2013-10-10 00:35:06 +03:00
subdomain = format_subdomain(subdomain)
2013-10-05 00:42:45 +03:00
if subdomain and not Wiki.is_registered(subdomain):
return redirect("http://%s/_new/?site=%s" % (config.hostname, subdomain))
return create_app(subdomain)
2013-10-04 05:57:19 +03:00
def create_app(subdomain=None):
app = Flask(__name__)
app.config.update(config.flask)
app.debug = (config.ENV is not 'PROD')
app.secret_key = config.secret_key
app.static_path = os.sep + 'static'
app.session_interface = RedisSessionInterface()
app.url_map.converters['regex'] = RegexConverter
2013-09-26 16:51:15 +03:00
2013-10-04 05:57:19 +03:00
login_manager.init_app(app)
2013-10-05 00:42:45 +03:00
login_manager.login_view = 'login'
2013-09-30 17:34:16 +03:00
2013-10-04 05:57:19 +03:00
@login_manager.user_loader
def load_user(user_id):
return CurrentUser(user_id)
2013-09-30 17:34:16 +03:00
2013-10-10 00:35:06 +03:00
assets.init_app(app)
if config.ENV is 'PROD':
if 'js_common' not in assets._named_bundles:
assets.register('js_common', Bundle('packed-common.js'))
if 'js_editor' not in assets._named_bundles:
assets.register('js_editor', Bundle('packed-editor.js'))
else:
if 'js_common' not in assets._named_bundles:
js = Bundle(
Bundle('vendor/jquery/jquery.js',
'vendor/components-underscore/underscore.js',
'vendor/components-bootstrap/js/bootstrap.js',
'vendor/handlebars/handlebars.js',
'vendor/showdown/src/showdown.js',
'vendor/showdown/src/extensions/table.js',
'js/wmd.js',
filters='closure_js'),
'js/html-sanitizer-minified.js',
'vendor/highlightjs/highlight.pack.js',
Bundle('js/main.js', filters='closure_js'),
output='packed-common.js')
assets.register('js_common', js)
if 'js_editor' not in assets._named_bundles:
js = Bundle('js/ace/ace.js',
'js/ace/mode-markdown.js',
'vendor/keymaster/keymaster.js',
'js/dillinger.js',
filters='closure_js', output='packed-editor.js')
assets.register('js_editor', js)
2013-10-03 17:58:07 +03:00
2013-10-04 05:57:19 +03:00
repo_dir = config.repos['dir']
2013-10-05 00:42:45 +03:00
repo_name = subdomain if subdomain else "_"
2013-10-05 00:42:45 +03:00
w = Wiki(repo_dir + "/" + repo_name)
2013-10-05 00:42:45 +03:00
@app.after_request
def inject_x_rate_headers(response):
limit = get_view_rate_limit()
if limit and limit.send_x_headers:
h = response.headers
h.add('X-RateLimit-Remaining', str(limit.remaining))
h.add('X-RateLimit-Limit', str(limit.limit))
h.add('X-RateLimit-Reset', str(limit.reset))
return response
2013-10-04 05:57:19 +03:00
@app.template_filter('datetime')
def _jinja2_filter_datetime(ts):
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
2013-09-26 16:51:15 +03:00
2013-10-04 05:57:19 +03:00
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
2013-09-26 16:51:15 +03:00
2013-10-04 05:57:19 +03:00
@app.errorhandler(500)
def page_error(e):
logging.exception(e)
return render_template('errors/500.html'), 500
2013-10-02 04:50:48 +03:00
2013-10-04 05:57:19 +03:00
@app.route("/")
2013-10-05 00:42:45 +03:00
@ratelimiter(limit=50, per=60)
2013-10-04 05:57:19 +03:00
def root():
return render('home')
@app.route("/home")
def home():
return redirect(url_for('root'))
2013-09-26 16:51:15 +03:00
2013-10-04 05:57:19 +03:00
@app.route("/account/")
2013-10-05 00:42:45 +03:00
@login_required
2013-10-04 05:57:19 +03:00
def account():
return render_template('account/index.html')
2013-09-26 16:51:15 +03:00
2013-10-04 05:57:19 +03:00
@app.route("/_new/", methods=['GET', 'POST'])
2013-10-05 00:42:45 +03:00
@login_required
2013-10-04 05:57:19 +03:00
def new_wiki():
if request.method == 'POST':
2013-10-05 00:42:45 +03:00
wiki_name = to_canonical(request.form['name'])
if Wiki.is_registered(wiki_name):
2013-10-04 05:57:19 +03:00
flash("Site already exists")
return redirect(redirect_url())
else:
2013-10-05 00:42:45 +03:00
s = Site()
2013-10-05 08:04:38 +03:00
s.create(name=wiki_name, repo=wiki_name, founder=CurrentUser.get('id'))
2013-10-05 00:42:45 +03:00
instances.pop(wiki_name, None)
2013-10-04 05:57:19 +03:00
return redirect('http://%s.%s' % (wiki_name, config.hostname))
else:
return render_template('_new/index.html')
2013-10-03 17:58:07 +03:00
2013-10-04 05:57:19 +03:00
@app.route("/logout/")
def logout():
2013-10-05 00:42:45 +03:00
User.logout()
2013-10-04 05:57:19 +03:00
return redirect(url_for('root'))
2013-10-03 17:58:07 +03:00
2013-10-04 05:57:19 +03:00
@app.route("/commit/<sha>/<name>")
def commit_sha(name, sha):
cname = to_canonical(name)
2013-10-04 05:57:19 +03:00
data = w.get_page(cname, sha=sha)
if data:
return render_template('page/page.html', page=data)
else:
2013-10-04 05:57:19 +03:00
return redirect('/create/'+cname)
@app.route("/compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
def compare(name, fsha, dots, lsha):
diff = w.compare(name, fsha, lsha)
return render_template('page/compare.html', name=name, diff=diff)
@app.route("/register", methods=['GET', 'POST'])
def register():
if request.method == 'POST':
2013-10-05 00:42:45 +03:00
if User.register(request.form.get('username'), request.form.get('email'), request.form.get('password')):
return redirect(url_for('root'))
else:
# Login failed
return redirect(url_for('register'))
2013-10-03 17:58:07 +03:00
else:
2013-10-04 05:57:19 +03:00
return render_template('account/register.html')
@app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
if User.auth(request.form['email'], request.form['password']):
2013-10-05 00:42:45 +03:00
return redirect(redirect_url(referrer=url_for('root')))
2013-10-04 05:57:19 +03:00
else:
flash("Email or Password invalid")
return redirect("/login")
2013-10-01 07:10:10 +03:00
else:
2013-10-04 05:57:19 +03:00
return render_template('account/login.html')
2013-10-01 07:10:10 +03:00
2013-10-04 05:57:19 +03:00
@app.route("/history/<name>")
def history(name):
history = w.get_history(name)
return render_template('page/history.html', name=name, history=history)
2013-10-04 05:57:19 +03:00
@app.route("/edit/<name>", methods=['GET', 'POST'])
2013-10-05 00:42:45 +03:00
@login_required
2013-10-04 05:57:19 +03:00
def edit(name):
data = w.get_page(name)
2013-10-02 04:50:48 +03:00
cname = to_canonical(name)
2013-10-04 05:57:19 +03:00
if request.method == 'POST':
edit_cname = to_canonical(request.form['name'])
2013-10-08 22:47:49 +03:00
if edit_cname.lower() != cname.lower():
2013-10-04 05:57:19 +03:00
w.rename_page(cname, edit_cname)
w.write_page(edit_cname, request.form['content'], message=request.form['message'],
username=CurrentUser.get('username'))
2013-10-04 05:57:19 +03:00
return redirect("/" + edit_cname)
else:
if data:
name = remove_ext(data['name'])
content = data['data']
return render_template('page/edit.html', name=name, content=content)
else:
return redirect('/create/'+cname)
@app.route("/delete/<name>", methods=['POST'])
2013-10-05 00:42:45 +03:00
@login_required
2013-10-04 05:57:19 +03:00
def delete(name):
pass
@app.route("/create/", methods=['GET', 'POST'])
@app.route("/create/<name>", methods=['GET', 'POST'])
2013-10-05 00:42:45 +03:00
@login_required
2013-10-04 05:57:19 +03:00
def create(name=None):
cname = ""
if name:
cname = to_canonical(name)
if w.get_page(cname):
# Page exists, edit instead
return redirect("/edit/" + cname)
if request.method == 'POST':
w.write_page(request.form['name'], request.form['content'], message=request.form['message'], create=True)
return redirect("/" + cname)
else:
return render_template('page/edit.html', name=cname, content="")
@app.route("/<name>")
def render(name):
cname = to_canonical(name)
if cname != name:
return redirect('/' + cname)
data = w.get_page(cname)
if data:
return render_template('page/page.html', name=cname, page=data)
else:
return redirect('/create/'+cname)
return app