2013-09-26 16:51:15 +03:00
|
|
|
import logging
|
2013-10-02 04:50:48 +03:00
|
|
|
import time
|
2013-12-03 22:09:57 +02:00
|
|
|
import sys
|
|
|
|
import os
|
2013-10-02 07:32:53 +03:00
|
|
|
|
2013-12-09 22:24:22 +02:00
|
|
|
from flask import Flask, request, render_template, url_for, redirect, session, g
|
2013-11-08 20:20:40 +02:00
|
|
|
from flask.ctx import _AppCtxGlobals
|
2013-12-03 22:09:57 +02:00
|
|
|
from flask.ext.script import Manager
|
2013-10-05 00:42:45 +03:00
|
|
|
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-03 17:58:07 +03:00
|
|
|
from werkzeug.routing import BaseConverter
|
2013-11-08 20:20:40 +02:00
|
|
|
from werkzeug.utils import cached_property
|
2013-10-05 00:42:45 +03:00
|
|
|
|
2013-12-03 22:09:57 +02:00
|
|
|
from realms import config
|
2013-11-08 20:20:40 +02:00
|
|
|
from realms.lib.ratelimit import get_view_rate_limit, ratelimiter
|
|
|
|
from realms.lib.session import RedisSessionInterface
|
|
|
|
from realms.lib.wiki import Wiki
|
|
|
|
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url
|
|
|
|
from realms.lib.services import db
|
2013-12-09 22:24:22 +02:00
|
|
|
from realms.models import User, CurrentUser
|
2013-11-08 20:20:40 +02:00
|
|
|
|
2013-10-05 00:42:45 +03:00
|
|
|
|
2013-12-09 22:24:22 +02:00
|
|
|
sites = {}
|
|
|
|
|
|
|
|
|
|
|
|
class Site(object):
|
|
|
|
wiki = None
|
2013-12-03 22:09:57 +02:00
|
|
|
|
|
|
|
|
2013-11-08 20:20:40 +02:00
|
|
|
class AppCtxGlobals(_AppCtxGlobals):
|
|
|
|
|
2013-12-03 22:09:57 +02:00
|
|
|
@cached_property
|
2013-12-09 22:24:22 +02:00
|
|
|
def current_site(self):
|
|
|
|
subdomain = format_subdomain(self.current_subdomain)
|
2013-12-03 22:09:57 +02:00
|
|
|
if not subdomain:
|
2013-12-09 22:24:22 +02:00
|
|
|
subdomain = "www"
|
2013-12-03 22:09:57 +02:00
|
|
|
|
2013-12-09 22:24:22 +02:00
|
|
|
if subdomain is "www" and self.current_subdomain:
|
|
|
|
# Invalid sub domain
|
|
|
|
return False
|
2013-12-03 22:09:57 +02:00
|
|
|
|
2013-12-09 22:24:22 +02:00
|
|
|
if not sites.get(subdomain):
|
|
|
|
sites[subdomain] = Site()
|
|
|
|
sites[subdomain].wiki = Wiki("%s/%s" % (config.REPO_DIR, subdomain))
|
|
|
|
|
|
|
|
return sites[subdomain]
|
2013-12-03 22:09:57 +02:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
@cached_property
|
2013-12-09 22:24:22 +02:00
|
|
|
def current_wiki(self):
|
|
|
|
return g.current_site.wiki
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def current_subdomain(self):
|
2013-12-03 22:09:57 +02:00
|
|
|
host = request.host.split(':')[0]
|
|
|
|
return host[:-len(config.DOMAIN)].rstrip('.')
|
2013-12-03 01:50:19 +02:00
|
|
|
|
2013-11-08 20:20:40 +02:00
|
|
|
@cached_property
|
|
|
|
def current_user(self):
|
|
|
|
return session.get('user') if session.get('user') else {'username': 'Anon'}
|
|
|
|
|
|
|
|
|
|
|
|
class Application(Flask):
|
|
|
|
app_ctx_globals_class = AppCtxGlobals
|
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
def __call__(self, environ, start_response):
|
|
|
|
path_info = environ.get('PATH_INFO')
|
2013-11-08 20:20:40 +02:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
if path_info and len(path_info) > 1 and path_info.endswith('/'):
|
|
|
|
environ['PATH_INFO'] = path_info[:-1]
|
2013-10-05 00:42:45 +03:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
scheme = environ.get('HTTP_X_SCHEME')
|
2013-10-05 00:42:45 +03:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
if scheme:
|
|
|
|
environ['wsgi.url_scheme'] = scheme
|
2013-10-05 00:42:45 +03:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
real_ip = environ.get('HTTP_X_REAL_IP')
|
2013-10-05 00:42:45 +03:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
if real_ip:
|
|
|
|
environ['REMOTE_ADDR'] = real_ip
|
2013-10-05 00:42:45 +03:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
return super(Application, self).__call__(environ, start_response)
|
2013-10-05 00:52:41 +03:00
|
|
|
|
2013-12-03 22:09:57 +02:00
|
|
|
def discover(self):
|
|
|
|
"""
|
|
|
|
Pattern taken from guildwork.com
|
|
|
|
"""
|
|
|
|
IMPORT_NAME = 'realms.modules'
|
|
|
|
FROMLIST = (
|
|
|
|
'assets',
|
|
|
|
'models',
|
|
|
|
'search',
|
|
|
|
'perms',
|
|
|
|
'broadcasts',
|
|
|
|
'commands',
|
|
|
|
'notifications',
|
|
|
|
'requests',
|
|
|
|
'tasks',
|
|
|
|
'views',
|
|
|
|
)
|
|
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
__import__(IMPORT_NAME, fromlist=FROMLIST)
|
|
|
|
|
|
|
|
for module_name in self.config['MODULES']:
|
|
|
|
sources = __import__('%s.%s' % (IMPORT_NAME, module_name), fromlist=FROMLIST)
|
|
|
|
|
|
|
|
# Blueprint
|
|
|
|
if hasattr(sources, 'views'):
|
|
|
|
self.register_blueprint(sources.views.blueprint)
|
|
|
|
|
|
|
|
# Flask-Script
|
|
|
|
if hasattr(sources, 'commands'):
|
|
|
|
manager.add_command(module_name, sources.commands.manager)
|
|
|
|
|
|
|
|
print >> sys.stderr, ' * Ready in %.2fms' % (1000.0 * (time.time() - start_time))
|
|
|
|
|
2013-10-05 00:52:41 +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-10 00:35:06 +03:00
|
|
|
def format_subdomain(s):
|
2013-12-03 22:09:57 +02:00
|
|
|
if not config.REPO_ENABLE_SUBDOMAIN:
|
2013-11-08 20:20:40 +02:00
|
|
|
return ""
|
2013-10-10 00:35:06 +03:00
|
|
|
s = s.lower()
|
|
|
|
s = to_canonical(s)
|
2013-12-03 22:09:57 +02:00
|
|
|
if s in config.REPO_FORBIDDEN_NAMES:
|
2013-10-10 00:35:06 +03:00
|
|
|
# Not allowed
|
|
|
|
s = ""
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
app = Application(__name__)
|
2013-12-03 22:09:57 +02:00
|
|
|
app.config.from_object('realms.config')
|
2013-12-03 01:50:19 +02:00
|
|
|
app.session_interface = RedisSessionInterface()
|
|
|
|
app.url_map.converters['regex'] = RegexConverter
|
2013-12-03 22:09:57 +02:00
|
|
|
app.url_map.strict_slashes = False
|
|
|
|
app.debug = True
|
|
|
|
|
|
|
|
manager = Manager(app)
|
2013-12-03 01:50:19 +02:00
|
|
|
|
|
|
|
# Flask extension objects
|
|
|
|
login_manager = LoginManager()
|
|
|
|
login_manager.init_app(app)
|
2013-12-04 00:28:16 +02:00
|
|
|
login_manager.login_view = 'auth.login'
|
2013-10-05 00:42:45 +03:00
|
|
|
|
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
@login_manager.user_loader
|
|
|
|
def load_user(user_id):
|
|
|
|
return CurrentUser(user_id)
|
2013-09-26 16:51:15 +03:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
assets = Environment()
|
|
|
|
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-12-09 22:24:22 +02:00
|
|
|
@app.before_request
|
|
|
|
def check_subdomain():
|
|
|
|
if not g.current_site:
|
|
|
|
return redirect('http://%s' % config.SERVER_NAME)
|
|
|
|
|
|
|
|
|
2013-12-03 01:50:19 +02: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
|
|
|
|
|
|
|
|
|
|
|
|
@app.template_filter('datetime')
|
|
|
|
def _jinja2_filter_datetime(ts):
|
|
|
|
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
|
|
|
|
|
2013-12-03 22:09:57 +02:00
|
|
|
|
2013-12-03 01:50:19 +02:00
|
|
|
@app.errorhandler(404)
|
|
|
|
def page_not_found(e):
|
|
|
|
return render_template('errors/404.html'), 404
|
|
|
|
|
|
|
|
|
|
|
|
@app.errorhandler(500)
|
|
|
|
def page_error(e):
|
|
|
|
logging.exception(e)
|
|
|
|
return render_template('errors/500.html'), 500
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/")
|
|
|
|
def root():
|
2013-12-03 22:09:57 +02:00
|
|
|
return redirect(url_for(config.ROOT_ENDPOINT))
|
2013-12-03 01:50:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/_account/")
|
|
|
|
@login_required
|
|
|
|
def account():
|
|
|
|
return render_template('account/index.html')
|
|
|
|
|
2013-12-03 22:09:57 +02:00
|
|
|
if 'devserver' not in sys.argv or os.environ.get('WERKZEUG_RUN_MAIN'):
|
|
|
|
app.discover()
|
2013-12-03 01:50:19 +02:00
|
|
|
|
2013-12-03 22:09:57 +02:00
|
|
|
print app.url_map
|
2013-10-04 05:57:19 +03:00
|
|
|
|