This commit is contained in:
Matthew Scragg 2014-01-13 17:07:13 -06:00
parent 99ce4acd00
commit 98c753aba6
12 changed files with 169 additions and 94 deletions

View file

@ -1,13 +1,20 @@
from gevent import monkey, wsgi
from gevent import wsgi
from realms import config, app, manager
monkey.patch_all()
@manager.command
def server(port=10000):
print "Server started (%s)" % config.ENV
wsgi.WSGIServer(('', int(port)), app).serve_forever()
@manager.command
def init_db():
from realms import db
import realms.models
print "Init DB"
db.drop_all()
db.create_all()
if __name__ == '__main__':
manager.run()

View file

@ -1,32 +1,46 @@
# Monkey patch stdlib.
import gevent.monkey
gevent.monkey.patch_all(aggressive=False)
# Set default encoding to UTF-8
import sys
reload(sys)
# noinspection PyUnresolvedReferences
sys.setdefaultencoding('utf-8')
# Silence Sentry and Requests.
import logging
logging.getLogger().setLevel(logging.INFO)
logging.getLogger('raven').setLevel(logging.WARNING)
logging.getLogger('requests').setLevel(logging.WARNING)
import time
import sys
import os
from flask import Flask, request, render_template, url_for, redirect, session, g
import httplib
import traceback
from flask import Flask, request, render_template, url_for, redirect, session, flash, g
from flask.ctx import _AppCtxGlobals
from flask.ext.script import Manager
from flask.ext.login import LoginManager, login_required
from flask.ext.assets import Environment, Bundle
from werkzeug.routing import BaseConverter
from werkzeug.utils import cached_property
from werkzeug.exceptions import HTTPException
from realms import config
from realms.lib.services import db
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
from realms.models import User, CurrentUser
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url, to_dict
from realms.models import User, CurrentUser, Site
sites = {}
class Site(object):
wiki = None
class AppCtxGlobals(_AppCtxGlobals):
@cached_property
@ -40,7 +54,7 @@ class AppCtxGlobals(_AppCtxGlobals):
return False
if not sites.get(subdomain):
sites[subdomain] = Site()
sites[subdomain] = to_dict(Site.get_by_name(subdomain))
sites[subdomain].wiki = Wiki("%s/%s" % (config.REPO_DIR, subdomain))
return sites[subdomain]
@ -149,9 +163,11 @@ app.url_map.converters['regex'] = RegexConverter
app.url_map.strict_slashes = False
app.debug = True
# Flask-SQLAlchemy
db.init_app(app)
manager = Manager(app)
# Flask extension objects
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
@ -161,6 +177,38 @@ login_manager.login_view = 'auth.login'
def load_user(user_id):
return CurrentUser(user_id)
def error_handler(e):
try:
if isinstance(e, HTTPException):
status_code = e.code
message = e.description if e.description != type(e).description else None
tb = None
else:
status_code = httplib.INTERNAL_SERVER_ERROR
message = None
tb = traceback.format_exc() if g.current_user.staff else None
if request.is_xhr or request.accept_mimetypes.best in ['application/json', 'text/javascript']:
response = {
'message': message,
'traceback': tb,
}
else:
response = render_template('errors/error.html',
title=httplib.responses[status_code],
status_code=status_code,
message=message,
traceback=tb)
except HTTPException as e2:
return error_handler(e2)
return response, status_code
for status_code in httplib.responses:
if status_code >= 400:
app.register_error_handler(status_code, error_handler)
assets = Environment()
assets.init_app(app)
if config.ENV is 'PROD':
@ -221,16 +269,25 @@ 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():
return redirect(url_for(config.ROOT_ENDPOINT))
@app.route("/new/", methods=['GET', 'POST'])
@login_required
def new():
if request.method == 'POST':
site_name = to_canonical(request.form['name'])
if Site.get_by_name(site_name):
flash("Site already exists")
return redirect(redirect_url())
else:
Site.create(name=site_name, founder=g.current_user.id)
return redirect('http://%s.%s' % (site_name, config.HOSTNAME))
else:
return render_template('wiki/new.html')
@app.route("/_account/")
@login_required
@ -240,5 +297,3 @@ def account():
if 'devserver' not in sys.argv or os.environ.get('WERKZEUG_RUN_MAIN'):
app.discover()
print app.url_map

View file

@ -2,10 +2,9 @@ import socket
HOSTNAME = socket.gethostname()
DOMAIN = 'realms.dev'
ENV = 'DEV'
DB_URI = 'postgresql://realms:dbpassword@localhost:5432/realms'
SQLALCHEMY_DATABASE_URI = 'postgresql://deploy:dbpassword@localhost:5432/realms'
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
@ -17,7 +16,6 @@ REPO_MAIN_NAME = '_'
REPO_FORBIDDEN_NAMES = ['api', 'www']
REPO_ENABLE_SUBDOMAIN = True
RECAPTCHA_PUBLIC_KEY = '6LfoxeESAAAAAGNaeWnISh0GTgDk0fBnr6Bo2Tfk'
RECAPTCHA_PRIVATE_KEY = '6LfoxeESAAAAABFzdCs0hNIIyeb42mofV-Ndd2_2'
RECAPTCHA_OPTIONS = {'theme': 'clean'}
@ -31,10 +29,12 @@ MODULES = [
]
if ENV is 'PROD':
SERVER_NAME = 'realms.io'
#SERVER_NAME = 'realms.io'
PORT = 80
DOMAIN = 'realms.io'
else:
DEBUG = True
ASSETS_DEBUG = True
SERVER_NAME = 'realms.dev:8000'
#SERVER_NAME = 'realms.dev:8000'
DOMAIN = 'realms.dev'
PORT = 8000

View file

@ -1,10 +1,8 @@
import redis
from sqlalchemy import create_engine
# Default DB connection
from flask.ext.sqlalchemy import SQLAlchemy
from realms import config
db = create_engine(config.DB_URI, encoding='utf8', echo=True)
db = SQLAlchemy()
# Default Cache connection
cache = redis.StrictRedis(host=config.REDIS_HOST, port=config.REDIS_PORT)

View file

@ -10,6 +10,34 @@ from realms import config
from realms.lib.services import cache
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
def to_json(data):
return json.dumps(to_dict(data), separators=(',', ':'))
def to_dict(data):
if not data:
return AttrDict()
def row2dict(row):
d = AttrDict()
for column in row.__table__.columns:
d[column.name] = getattr(row, column.name)
return d
if isinstance(data, list):
return [row2dict(x) for x in data]
else:
return row2dict(data)
def cache_it(fn):
def wrap(*args, **kw):
key = "%s:%s" % (args[0].table, args[1])
@ -40,26 +68,6 @@ def cache_it(fn):
return wrap
def to_json(res, first=False):
"""
Jsonify query result.
"""
res = to_dict(res, first)
return json.dumps(res, separators=(',', ':'))
def to_dict(cur, first=False):
if not cur:
return None
ret = []
for row in cur:
ret.append(row)
if ret and first:
return ret[0]
else:
return ret
def validate_captcha():
response = captcha.submit(
request.form['recaptcha_challenge_field'],

View file

@ -63,11 +63,6 @@ class Wiki():
self.path = path
@staticmethod
def is_registered(name):
s = Site()
return True if s.get_by_name(name) else False
def revert_page(self, name, commit_sha, message, username):
page = self.get_page(name, commit_sha)
if not page:

View file

@ -1,12 +1,21 @@
import bcrypt
from sqlalchemy import Column, Integer, String, Time
from sqlalchemy.ext.declarative import declarative_base
from flask import session, flash
from flask.ext.login import login_user, logout_user
from realms.lib.util import gravatar_url, to_dict
from realms.lib.services import db
from realms.lib.util import gravatar_url, to_dict
Base = declarative_base()
class ModelMixin(object):
def __getitem__(self, k):
return self.__getattribute__(k)
@classmethod
def create(cls, **kwargs):
obj = cls(**kwargs)
db.session.add(obj)
db.session.commit()
return obj
class CurrentUser():
@ -15,8 +24,7 @@ class CurrentUser():
def __init__(self, id):
self.id = id
if id:
user = User()
session['user'] = user.get_by_id(id)
session['user'] = to_dict(User.query.filter_by(id=id).first())
def get_id(self):
return self.id
@ -38,28 +46,38 @@ class CurrentUser():
return None
class Site(Base):
class Site(ModelMixin, db.Model):
__tablename__ = 'sites'
id = Column(Integer, primary_key=True)
name = Column(String(100))
pages = Column(Integer)
views = Column(Integer)
created = Column(Time)
founder = Column(Integer)
created_at = Column(Time)
updated_at = Column(Time)
@classmethod
def get_by_name(cls, name):
return Site.query.filter_by(name=name).first()
class User(Base):
class User(db.Model, ModelMixin):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(100))
email = Column(String(255))
avatar = Column(String(255))
password = Column(String(255))
joined = Column(Time)
created_at = Column(Time)
updated_at = Column(Time)
def get_by_email(self, email):
return to_dict(self.get_one(email, 'email'), True)
@classmethod
def get_by_email(cls, email):
return User.query.filter_by(email=email).first()
def get_by_username(self, username):
return to_dict(self.get_one(username, 'username'), True)
@classmethod
def get_by_username(cls, username):
return User.query.filter_by(username=username).first()
def login(self, login, password):
pass
@ -84,6 +102,7 @@ class User(Base):
if user.get_by_email(email):
flash('Email is already taken')
return False
if user.get_by_username(username):
flash('Username is already taken')
return False
@ -93,7 +112,6 @@ class User(Base):
username=username,
password=bcrypt.hashpw(password, bcrypt.gensalt(10)),
avatar=gravatar_url(email))
User.login(u.id)
@classmethod

View file

@ -1,30 +1,10 @@
from flask import g, render_template, request, redirect, Blueprint, flash, url_for
from flask.ext.login import login_required
from realms import redirect_url, config
from realms.lib.util import to_canonical, remove_ext
from realms.lib.wiki import Wiki
from realms.models import Site
blueprint = Blueprint('wiki', __name__, url_prefix='/wiki')
@blueprint.route("/new/", methods=['GET', 'POST'])
@login_required
def new():
if request.method == 'POST':
wiki_name = to_canonical(request.form['name'])
if Wiki.is_registered(wiki_name):
flash("Site already exists")
return redirect(redirect_url())
else:
s = Site()
s.create(name=wiki_name, repo=wiki_name, founder=g.current_user.get('id'))
return redirect('http://%s.%s' % (wiki_name, config.HOSTNAME))
else:
return render_template('wiki/new.html')
@blueprint.route("/_commit/<sha>/<name>")
def commit(name, sha):
cname = to_canonical(name)

View file

@ -0,0 +1,17 @@
{% extends "layout.html" %}
{% block title %}{{ title|escape }}{% endblock %}
{% block scripts %}
{% if traceback %}
<link rel="stylesheet" href="{{ static_url('vendor/highlightjs/default.css') }}" />
<script src="{{ static_url('vendor/highlightjs/highlight.pack.js') }}"></script>
<script>hljs.initHighlightingOnLoad();</script>
{% endif %}
{% endblock %}
{% block content %}
<div class="page-header"><h1>{{ title }} <small>{{ status_code }}</small></h1></div>
{% if message %}<div class="alert alert-error">{{ message|escape }}</div>{% endif %}
{% if traceback %}<pre><code>{{ traceback|escape }}</code></pre>{% endif %}
<a href="{{ url_for('home.index') }}" class="btn btn-danger btn-large">&larr; Back to Safety</a>
<hr />
<div class="help-block">Think you know what happened? Please <a href="mailto:support@realms.io">email</a> us.</div>
{% endblock %}

View file

@ -48,9 +48,6 @@
<li><a href="{{ url_for('wiki.edit', name=name) }}">Edit Page</a></li>
<li><a href="{{ url_for('wiki.history', name=name) }}">History</a></li>
{% endif %}
<li class="divider"></li>
<li class="dropdown-header">Site Options</li>
<li><a href="{{ url_for('wiki.new') }}">Create New Site</a></li>
</ul>
</li>

View file

@ -3,6 +3,7 @@ Flask-Assets==0.8
Flask-Bcrypt==0.5.2
Flask-Login==0.2.7
Flask-Script==0.6.3
Flask-SQLAlchemy==1.0
beautifulsoup4==4.3.2
boto==2.17.0
closure==20121212
@ -16,7 +17,6 @@ pyzmq==14.0.0
recaptcha==1.0rc1
recaptcha-client==1.0.6
redis==2.8.0
rethinkdb==1.10.0-0
simplejson==3.3.1
sockjs-tornado==1.0.0
supervisor==3.0