diff --git a/app.py b/manage.py similarity index 58% rename from app.py rename to manage.py index 7cc259b..6a66516 100644 --- a/app.py +++ b/manage.py @@ -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() diff --git a/realms/__init__.py b/realms/__init__.py index ec98279..79e4168 100644 --- a/realms/__init__.py +++ b/realms/__init__.py @@ -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 - diff --git a/realms/config/__init__.py b/realms/config/__init__.py index b9195df..bbc066b 100644 --- a/realms/config/__init__.py +++ b/realms/config/__init__.py @@ -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 \ No newline at end of file diff --git a/realms/lib/services.py b/realms/lib/services.py index 4d54fe0..35beb46 100644 --- a/realms/lib/services.py +++ b/realms/lib/services.py @@ -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) \ No newline at end of file diff --git a/realms/lib/util.py b/realms/lib/util.py index 227f0d2..f6e21ad 100644 --- a/realms/lib/util.py +++ b/realms/lib/util.py @@ -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'], diff --git a/realms/lib/wiki.py b/realms/lib/wiki.py index d79904c..3221f87 100644 --- a/realms/lib/wiki.py +++ b/realms/lib/wiki.py @@ -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: diff --git a/realms/models.py b/realms/models.py index 7219ed4..63906a6 100644 --- a/realms/models.py +++ b/realms/models.py @@ -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 diff --git a/realms/modules/wiki/views.py b/realms/modules/wiki/views.py index c6666df..930e939 100644 --- a/realms/modules/wiki/views.py +++ b/realms/modules/wiki/views.py @@ -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//") def commit(name, sha): cname = to_canonical(name) diff --git a/realms/templates/errors/500.html b/realms/templates/errors/500.html deleted file mode 100644 index e69de29..0000000 diff --git a/realms/templates/errors/error.html b/realms/templates/errors/error.html new file mode 100644 index 0000000..818238a --- /dev/null +++ b/realms/templates/errors/error.html @@ -0,0 +1,17 @@ +{% extends "layout.html" %} +{% block title %}{{ title|escape }}{% endblock %} +{% block scripts %} + {% if traceback %} + + + + {% endif %} +{% endblock %} +{% block content %} + + {% if message %}
{{ message|escape }}
{% endif %} + {% if traceback %}
{{ traceback|escape }}
{% endif %} + ← Back to Safety +
+
Think you know what happened? Please email us.
+{% endblock %} \ No newline at end of file diff --git a/realms/templates/layout.html b/realms/templates/layout.html index 37b2a2f..df5eb09 100644 --- a/realms/templates/layout.html +++ b/realms/templates/layout.html @@ -48,9 +48,6 @@
  • Edit Page
  • History
  • {% endif %} -
  • - -
  • Create New Site
  • diff --git a/requirements.txt b/requirements.txt index bfca8c2..33e27cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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