registration and other stuff
This commit is contained in:
parent
613d1c6ca3
commit
cba28239a2
|
@ -4,11 +4,12 @@ import time
|
|||
|
||||
import redis
|
||||
import rethinkdb as rdb
|
||||
from flask import Flask, request, render_template, url_for, redirect
|
||||
from flask import Flask, request, render_template, url_for, redirect, flash, session
|
||||
from flask.ext.bcrypt import Bcrypt
|
||||
from flask.ext.login import LoginManager
|
||||
from flask.ext.login import LoginManager, login_user, logout_user
|
||||
from flask.ext.assets import Environment
|
||||
from recaptcha.client import captcha
|
||||
from werkzeug.routing import BaseConverter
|
||||
|
||||
import config
|
||||
from session import RedisSessionInterface
|
||||
|
@ -16,12 +17,18 @@ from wiki import Wiki
|
|||
from util import to_canonical, remove_ext
|
||||
|
||||
|
||||
class RegexConverter(BaseConverter):
|
||||
def __init__(self, url_map, *items):
|
||||
super(RegexConverter, self).__init__(url_map)
|
||||
self.regex = items[0]
|
||||
|
||||
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
|
||||
|
||||
bcrypt = Bcrypt(app)
|
||||
|
||||
|
@ -45,7 +52,13 @@ if not config.db['dbname'] in rdb.db_list().run(conn) and config.ENV is not 'PRO
|
|||
|
||||
repo_dir = config.repo['dir']
|
||||
|
||||
from models import Site
|
||||
# This is down here because of dependencies above
|
||||
from models import Site, User, CurrentUser
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return CurrentUser(user_id)
|
||||
|
||||
w = Wiki(repo_dir)
|
||||
|
||||
|
@ -54,6 +67,14 @@ def redirect_url():
|
|||
return request.args.get('next') or request.referrer or url_for('index')
|
||||
|
||||
|
||||
def validate_captcha():
|
||||
response = captcha.submit(
|
||||
request.form['recaptcha_challenge_field'],
|
||||
request.form['recaptcha_response_field'],
|
||||
app.config['RECAPTCHA_PRIVATE_KEY'],
|
||||
request.remote_addr)
|
||||
return response.is_valid
|
||||
|
||||
@app.template_filter('datetime')
|
||||
def _jinja2_filter_datetime(ts):
|
||||
return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts))
|
||||
|
@ -72,7 +93,18 @@ def page_error(e):
|
|||
|
||||
@app.route("/")
|
||||
def root():
|
||||
return redirect('/home')
|
||||
return render('home')
|
||||
#return redirect('/home')
|
||||
|
||||
@app.route("/account/")
|
||||
def account():
|
||||
return render_template('account/index.html')
|
||||
|
||||
@app.route("/logout/")
|
||||
def logout():
|
||||
logout_user()
|
||||
del session['user']
|
||||
return redirect(url_for('root'))
|
||||
|
||||
@app.route("/commit/<sha>/<name>")
|
||||
def commit_sha(name, sha):
|
||||
|
@ -85,18 +117,29 @@ def commit_sha(name, sha):
|
|||
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':
|
||||
response = captcha.submit(
|
||||
request.form['recaptcha_challenge_field'],
|
||||
request.form['recaptcha_response_field'],
|
||||
app.config['RECAPTCHA_PRIVATE_KEY'],
|
||||
request.remote_addr)
|
||||
if not response.is_valid:
|
||||
return redirect('/register?fail')
|
||||
else:
|
||||
return redirect("/")
|
||||
user = User()
|
||||
if user.get_by_email(request.form['email']):
|
||||
flash('Email is already taken')
|
||||
return redirect('/register')
|
||||
if user.get_by_username(request.form['username']):
|
||||
flash('Username is already taken')
|
||||
return redirect('/register')
|
||||
|
||||
# Create user and login
|
||||
u = User.create(email=request.form['email'].lower(),
|
||||
username=request.form['username'],
|
||||
password=bcrypt.generate_password_hash(request.form['password']))
|
||||
login_user(u)
|
||||
return redirect("/")
|
||||
else:
|
||||
return render_template('account/register.html')
|
||||
|
||||
|
@ -104,7 +147,11 @@ def register():
|
|||
@app.route("/login", methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
pass
|
||||
if User.auth(request.form['email'], request.form['password']):
|
||||
return redirect("/")
|
||||
else:
|
||||
flash("Email or Password invalid")
|
||||
return redirect("/login")
|
||||
else:
|
||||
return render_template('account/login.html')
|
||||
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
import rethinkdb as rdb
|
||||
from flask import session
|
||||
from flask.ext.login import login_user
|
||||
from rethinkORM import RethinkModel
|
||||
from reimagine import conn, bcrypt
|
||||
|
||||
from reimagine import conn
|
||||
|
||||
def to_dict(cur, first=False):
|
||||
ret = []
|
||||
for row in cur:
|
||||
ret.append(row)
|
||||
if ret and first:
|
||||
return ret[0]
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
class BaseModel(RethinkModel):
|
||||
|
@ -14,14 +26,63 @@ class BaseModel(RethinkModel):
|
|||
def create(cls, **kwargs):
|
||||
return super(BaseModel, cls).create(**kwargs)
|
||||
|
||||
def get_all(self, arg, index):
|
||||
return rdb.table(self.table).get_all(arg, index=index).run(self._conn)
|
||||
|
||||
def get_one(self, arg, index):
|
||||
return rdb.table(self.table).get_all(arg, index=index).limit(1).run(self._conn)
|
||||
|
||||
|
||||
class Site(BaseModel):
|
||||
table = 'sites'
|
||||
|
||||
|
||||
class CurrentUser():
|
||||
id = None
|
||||
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
def is_anonymous(self):
|
||||
return False if self.id else True
|
||||
|
||||
def is_authenticated(self):
|
||||
return True if self.id else False
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
table = 'users'
|
||||
|
||||
def get_by_email(self, email):
|
||||
return to_dict(self.get_one(email, 'email'), True)
|
||||
|
||||
def get_by_username(self, username):
|
||||
return to_dict(self.get_one(username, 'username'), True)
|
||||
|
||||
def login(self, login, password):
|
||||
pass
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get(cls, id):
|
||||
print id
|
||||
return cls(id=id)
|
||||
|
||||
@classmethod
|
||||
def auth(cls, username, password):
|
||||
u = User()
|
||||
data = u.get_by_email(username)
|
||||
if not data:
|
||||
return False
|
||||
|
||||
if bcrypt.check_password_hash(data['password'], password):
|
||||
login_user(CurrentUser(data['id']))
|
||||
session['user'] = data
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -1,5 +1,15 @@
|
|||
.navbar {
|
||||
margin-bottom: 25px;
|
||||
|
||||
}
|
||||
|
||||
#main-body {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
margin: 0 -20px;
|
||||
-webkit-border-radius: 0 0 6px 6px;
|
||||
border-radius: 0 0 6px 6px;
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
|
@ -26,7 +36,7 @@
|
|||
left: 50%;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
top: 60px;
|
||||
top: 50px;
|
||||
overflow: auto;
|
||||
background: rgba(255,255,255,0.9);
|
||||
border: 1px solid #EEE;
|
||||
|
@ -58,7 +68,7 @@
|
|||
#editor {
|
||||
position: absolute;
|
||||
margin-right: 5px;
|
||||
top: 60px;
|
||||
top: 50px;
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
right: 50%;
|
||||
|
@ -85,4 +95,27 @@
|
|||
-ms-box-flex: 1;
|
||||
-o-box-flex: 1;
|
||||
box-flex: 1;
|
||||
}
|
||||
|
||||
.user-avatar a img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
-webkit-box-shadow: 0 1px 3px #1e1e1e;
|
||||
-moz-box-shadow: 0 1px 3px #1e1e1e;
|
||||
box-shadow: 0 1px 3px #1e1e1e;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
-ms-border-radius: 2px;
|
||||
-o-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
|
||||
.navbar-nav .user-avatar a {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.navbar-nav>li.user-avatar a {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
}
|
20
reimagine/templates/account/index.html
Normal file
20
reimagine/templates/account/index.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<h2>Account</h2>
|
||||
|
||||
<form method="POST" role="form" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="email" class="col-md-2 control-label">Email</label>
|
||||
<div class="col-md-10">
|
||||
<input id="email" type="text" class="form-control" value="{{ session['user']['email'] }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-offset-2 col-md-10">
|
||||
<input type="submit" class="btn btn-primary" value="Save">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
<form role="form" method="post">
|
||||
<div class="form-group">
|
||||
<label for="username">Username or Email</label>
|
||||
<input type="text" class="form-control" id="username" name="username" />
|
||||
<label for="email">Email Address</label>
|
||||
<input type="text" class="form-control" id="email" name="email" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -27,4 +27,6 @@
|
|||
<input type="submit" class="btn btn-primary" value="Submit" />
|
||||
</form>
|
||||
|
||||
<a href="/login" class="pull-right">Already registered? Login here.</a>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<h1>Page Not Found</h1>
|
||||
|
||||
{% endblock %}
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>ReImagine</title>
|
||||
<title>Realms</title>
|
||||
|
||||
<link href="/static/css/cerulean.bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/static/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
@ -27,7 +27,7 @@
|
|||
<body>
|
||||
|
||||
<!-- Fixed navbar -->
|
||||
<div class="navbar navbar-default navbar-fixed-top">
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
|
@ -35,24 +35,54 @@
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">ReImagine</a>
|
||||
<a class="navbar-brand" href="/">Realms</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<div class="navbar-collapse collapse navbar-inverse-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li><a href="/create/"><i class="icon-plus"></i> Create</a></li>
|
||||
{% if name %}
|
||||
<li><a href="/edit/{{- name -}}"><i class="icon-edit"></i> Edit</a></li>
|
||||
<li><a href="/history/{{- name -}}"><i class="icon-time"></i> History</a></li>
|
||||
{% if session.get('user') %}
|
||||
<li class="dropdown user-avatar">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<span>
|
||||
<img src="http://static.ffxiah.com/images/avatars/mini/5a85e45e0976c8a96c955e83a9743b47.jpg" class="menu-avatar">
|
||||
<span>{{ session['user'].get('username') }} <i class="icon-caret-down"></i></span>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/account">Account</a></li>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="/login"><i class="icon-user"></i> Login</a></li>
|
||||
<li><a href="/register"><i class="icon-pencil"></i> Register</a></li>
|
||||
{% endif %}
|
||||
<li><a href="/login"><i class="icon-user"></i> Login</a></li>
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">{% block body %}{% endblock %}</div>
|
||||
|
||||
<div class="container">
|
||||
<div id="main-body">
|
||||
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
{% if category == 'message' %}
|
||||
{% set category = "info" %}
|
||||
{% endif %}
|
||||
<div class='alert alert-{{ category }}'>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/jquery-1.10.2.min.js"></script>
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
|
|
16
reimagine/templates/page/compare.html
Normal file
16
reimagine/templates/page/compare.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<h2>History for <strong>{{ name }}</strong></h2>
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm" href="/history/{{ name }}">Back to History</a>
|
||||
<a href="" class="btn btn-default btn-sm">Revert Changes</a>
|
||||
</p>
|
||||
|
||||
{{ diff|safe }}
|
||||
<p></p>
|
||||
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm" href="/history/{{ name }}">Back to History</a>
|
||||
</p>
|
||||
{% endblock %}
|
|
@ -10,16 +10,16 @@
|
|||
<div id="app-wrap" class="container-fluid">
|
||||
<div id="app-controls" class="row">
|
||||
<div class="col-xs-3">
|
||||
<input id="page-name" type="text" class="form-control" name="name" placeholder="Name" value="{{- name -}}" />
|
||||
<input id="page-name" type="text" class="form-control input-sm" name="name" placeholder="Name" value="{{- name -}}" />
|
||||
</div>
|
||||
<div class="col-xs-3">
|
||||
<input id="page-message" type="text" class="form-control" name="page-message" placeholder="Comment" value="" />
|
||||
<input id="page-message" type="text" class="form-control input-sm" name="page-message" placeholder="Comment" value="" />
|
||||
</div>
|
||||
|
||||
<div class="col-xs-6">
|
||||
<div class="pull-right">
|
||||
|
||||
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default" data-toggle="dropdown">Theme <b class="caret"></b></a>
|
||||
<a href="#" id="drop6" role="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">Theme <b class="caret"></b></a>
|
||||
<ul id="theme-list" class="dropdown-menu" role="menu" aria-labelledby="drop6">
|
||||
<li><a tabindex="-1" href="#" data-value="ace/theme/chrome" class="">Chrome</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="ace/theme/clouds" class="">Clouds</a></li>
|
||||
|
@ -47,7 +47,7 @@
|
|||
<li><a tabindex="-1" href="#" data-value="ace/theme/twilight" class="">Twilight</a></li>
|
||||
<li><a tabindex="-1" href="#" data-value="ace/theme/vibrant_ink" class="">Vibrant Ink</a></li>
|
||||
</ul>
|
||||
<a id="save-native" class="btn btn-primary"><i class="icon-save"></i> Save</a>
|
||||
<a id="save-native" class="btn btn-primary btn-sm"><i class="icon-save"></i> Save</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,58 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<h2>History</h2>
|
||||
<table class="table table-bordered">
|
||||
<h2>History for <strong>{{ name }}</strong></h2>
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
|
||||
</p>
|
||||
|
||||
<table class="table table-bordered revision-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Revision Message</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for h in history %}
|
||||
<tr>
|
||||
<td class="checkbox-cell text-center"><input type="checkbox" /></td>
|
||||
<td class="checkbox-cell text-center">
|
||||
<input type="checkbox" name="versions[]" value="{{ h.sha }}" />
|
||||
</td>
|
||||
<td>{{ h.author }}</td>
|
||||
<td><a href="/commit/{{ h.sha }}/{{ name }}" class='label label-primary'>{{ h.sha|truncate(7, True, end="") }}</a> {{ h.message }} </td>
|
||||
<td>{{ h.time|datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<p>
|
||||
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
$(function(){
|
||||
$('.revision-tbl :checkbox').change(function () {
|
||||
var $cs=$(this).closest('.revision-tbl').find(':checkbox:checked');
|
||||
if ($cs.length > 2) {
|
||||
this.checked=false;
|
||||
}
|
||||
});
|
||||
|
||||
$(".compare-revisions").click(function(){
|
||||
var $cs = $('.revision-tbl').find(':checkbox:checked');
|
||||
console.log($cs.length);
|
||||
if ($cs.length != 2) return;
|
||||
var revs = [];
|
||||
$.each($cs, function(i, v){
|
||||
revs.push(v.value);
|
||||
});
|
||||
revs = revs.join("...");
|
||||
location.href = "/compare/{{ name }}/" + revs;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,15 +1,20 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<div id="page-content" style="display:none">
|
||||
{{ page.data|safe }}
|
||||
</div>
|
||||
<div class="controls pull-right">
|
||||
<a class="btn btn-default btn-sm" href="/edit/{{ name }}">Edit</a>
|
||||
<a class="btn btn-default btn-sm" href="/history/{{ name }}">History</a>
|
||||
</div>
|
||||
|
||||
<div id="page-content" style="display:none">
|
||||
{{ page.data|safe }}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script>
|
||||
$(function(){
|
||||
$("#page-content").html(converter({{ page.data|tojson|safe }})).show();
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(function(){
|
||||
$("#page-content").html(converter({{ page.data|tojson|safe }})).show();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,11 +1,11 @@
|
|||
import os
|
||||
|
||||
import ghdiff
|
||||
from gittle import Gittle
|
||||
|
||||
from util import to_canonical
|
||||
|
||||
|
||||
class MyGittle(Gittle):
|
||||
|
||||
def file_history(self, path):
|
||||
"""Returns all commits where given file was modified
|
||||
"""
|
||||
|
@ -57,7 +57,7 @@ class Wiki():
|
|||
|
||||
self.path = path
|
||||
|
||||
def write_page(self, name, content, message=None, create=False):
|
||||
def write_page(self, name, content, message=None, create=False, username=None, email=None):
|
||||
#content = clean_html(content)
|
||||
filename = self.cname_to_filename(to_canonical(name))
|
||||
f = open(self.path + "/" + filename, 'w')
|
||||
|
@ -67,8 +67,16 @@ class Wiki():
|
|||
if create:
|
||||
self.repo.add(filename)
|
||||
|
||||
return self.repo.commit(name=self.default_committer_name,
|
||||
email=self.default_committer_email,
|
||||
if not message:
|
||||
message = "Updated %s" % name
|
||||
|
||||
if not username:
|
||||
username = self.default_committer_name
|
||||
email = "%s@domain.com" % username
|
||||
|
||||
|
||||
return self.repo.commit(name=username,
|
||||
email=email,
|
||||
message=message,
|
||||
files=[filename])
|
||||
|
||||
|
@ -88,6 +96,12 @@ class Wiki():
|
|||
# HEAD doesn't exist yet
|
||||
return None
|
||||
|
||||
def compare(self, name, new_sha, old_sha):
|
||||
old = self.get_page(name, sha=old_sha)
|
||||
new = self.get_page(name, sha=new_sha)
|
||||
|
||||
return ghdiff.diff(old['data'], new['data'])
|
||||
|
||||
def get_history(self, name):
|
||||
return self.repo.file_history(self.cname_to_filename(name))
|
||||
|
||||
|
|
Loading…
Reference in a new issue