avatars and invitation requests done.
This commit is contained in:
parent
95657606bc
commit
bc7af104c4
12 changed files with 182 additions and 25 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@
|
|||
.bundle/config
|
||||
logs/
|
||||
files/
|
||||
pfp/
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
|
|
2
deploy
2
deploy
|
@ -10,4 +10,4 @@ sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
|
|||
echo "Starting the server..."
|
||||
cd /home/vagrant/server
|
||||
sudo service nginx start
|
||||
tmux new-session -s ctf -d 'gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'
|
||||
tmux new-session -s ctf -d 'tmux set remain-on-exit on && gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'
|
|
@ -4,4 +4,5 @@ Flask-SQLAlchemy
|
|||
SQLAlchemy
|
||||
gunicorn
|
||||
requests
|
||||
voluptuous
|
||||
voluptuous
|
||||
PIL
|
|
@ -12,7 +12,7 @@ sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again p
|
|||
|
||||
echo "Installing dependencies..."
|
||||
apt-get -y install python
|
||||
apt-get -y install python-pip
|
||||
apt-get -y install python-pip libjpeg-dev
|
||||
apt-get -y install python-dev libmysqlclient-dev
|
||||
apt-get -y install nginx
|
||||
apt-get -y install mysql-server
|
||||
|
|
|
@ -36,7 +36,7 @@ def team_create():
|
|||
Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid })
|
||||
db.session.commit()
|
||||
|
||||
session["tid"] = tid
|
||||
session["tid"] = team.tid
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/delete", methods=["POST"])
|
||||
|
@ -175,6 +175,9 @@ def team_accept_invite():
|
|||
if _team is None:
|
||||
raise WebException("Team not found.")
|
||||
|
||||
if len(_team.get_members()) >= 5:
|
||||
raise WebException("This team is full.")
|
||||
|
||||
invitation = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user.uid).first()
|
||||
if invitation is None:
|
||||
raise WebException("Invitation doesn't exist.")
|
||||
|
@ -190,6 +193,41 @@ def team_accept_invite():
|
|||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/invite/request/accept", methods=["POST"])
|
||||
@api_wrapper
|
||||
def team_accept_invite_request():
|
||||
params = utils.flat_multi(request.form)
|
||||
_user = user.get_user().first()
|
||||
if not user.in_team(_user):
|
||||
raise WebException("You must be in a team!")
|
||||
_team = get_team(tid=_user.tid).first()
|
||||
tid = _team.tid
|
||||
if _user.uid != _team.owner:
|
||||
raise WebException("You must be the captain of your team to rescind invitations!")
|
||||
|
||||
if len(_team.get_members()) >= 5:
|
||||
raise WebException("Your team is full.")
|
||||
|
||||
uid = params.get("uid")
|
||||
_user2 = user.get_user(uid=uid).first()
|
||||
if user.in_team(_user2):
|
||||
raise WebException("This user is already in a team!")
|
||||
|
||||
invitation = TeamInvitations.query.filter_by(rtype=1, frid=_user2.uid, toid=tid).first()
|
||||
if invitation is None:
|
||||
raise WebException("Invitation doesn't exist.")
|
||||
|
||||
with app.app_context():
|
||||
_user2 = Users.query.filter_by(uid=_user2.uid).first()
|
||||
_user2.tid = tid
|
||||
db.session.delete(invitation)
|
||||
invitation2 = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user2.uid).first()
|
||||
if invitation2 is not None:
|
||||
db.session.delete(invitation2)
|
||||
db.session.commit()
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/info", methods=["GET"])
|
||||
@api_wrapper
|
||||
def team_info():
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask import Blueprint, make_response, session, request, redirect, url_for
|
||||
from flask import Blueprint, make_response, session, request, redirect, url_for, send_file
|
||||
from flask import current_app as app
|
||||
from voluptuous import Schema, Length, Required
|
||||
|
||||
|
@ -88,6 +88,7 @@ def user_register():
|
|||
with app.app_context():
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
utils.generate_identicon(email, user.uid)
|
||||
|
||||
logger.log(__name__, logger.INFO, "%s registered with %s" % (name.encode("utf-8"), email.encode("utf-8")))
|
||||
login_user(username, password)
|
||||
|
@ -160,7 +161,8 @@ def user_info():
|
|||
"registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z",
|
||||
"me": me,
|
||||
"show_email": show_email,
|
||||
"in_team": user_in_team
|
||||
"in_team": user_in_team,
|
||||
"uid": user.uid
|
||||
}
|
||||
if show_email:
|
||||
userdata["email"] = user.email
|
||||
|
@ -171,6 +173,18 @@ def user_info():
|
|||
userdata["invitations"] = invitations
|
||||
return { "success": 1, "user": userdata }
|
||||
|
||||
@blueprint.route("/avatar/<uid>", methods=["GET"])
|
||||
def user_avatar(uid):
|
||||
uid = int(uid)
|
||||
try:
|
||||
return send_file("pfp/%d.png" % uid, mimetype="image/png")
|
||||
except:
|
||||
user = get_user(uid=uid).first()
|
||||
if user is not None:
|
||||
utils.generate_identicon(user.email, user.uid)
|
||||
return send_file("pfp/%d.png" % uid, mimetype="image/png")
|
||||
return abort(404)
|
||||
|
||||
##################
|
||||
# USER FUNCTIONS #
|
||||
##################
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
|
@ -7,6 +8,8 @@ import string
|
|||
import traceback
|
||||
import unicodedata
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from flask import current_app as app
|
||||
from functools import wraps
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
@ -39,11 +42,61 @@ def flat_multi(multidict):
|
|||
return flat
|
||||
|
||||
def send_email(recipient, subject, body):
|
||||
api_key = app.config["MG_API_KEY"]
|
||||
data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]),
|
||||
"to": recipient,
|
||||
"subject": subject,
|
||||
"text": body
|
||||
}
|
||||
auth = ("api", api_key)
|
||||
return requests.post("https://api.mailgun.net/v3/%s/messages" % (app.config["MG_HOST"]), auth=auth, data=data)
|
||||
api_key = app.config["MG_API_KEY"]
|
||||
data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]),
|
||||
"to": recipient,
|
||||
"subject": subject,
|
||||
"text": body
|
||||
}
|
||||
auth = ("api", api_key)
|
||||
return requests.post("https://api.mailgun.net/v3/%s/messages" % (app.config["MG_HOST"]), auth=auth, data=data)
|
||||
|
||||
def generate_identicon(email, filename):
|
||||
email = email.strip().lower()
|
||||
h = hashlib.sha1(email).hexdigest()
|
||||
size = 256
|
||||
margin = 0.08
|
||||
baseMargin = int(size * margin)
|
||||
cell = int((size - baseMargin * 2.0) / 5)
|
||||
margin = int((size - cell * 5.0) / 2)
|
||||
image = Image.new("RGB", (size, size))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
def hsl2rgb(h, s, b):
|
||||
h *= 6
|
||||
s1 = []
|
||||
s *= b if b < 0.5 else 1-b
|
||||
b += s
|
||||
s1.append(b)
|
||||
s1.append(b - h % 1 * s * 2)
|
||||
s *= 2
|
||||
b -= s
|
||||
s1.append(b)
|
||||
s1.append(b)
|
||||
s1.append(b + h % 1 * s)
|
||||
s1.append(b + s)
|
||||
|
||||
return [
|
||||
s1[~~h % 6], s1[(h|16) % 6], s1[(h|8) % 6]
|
||||
]
|
||||
|
||||
rgb = hsl2rgb(int(h[-7:], 16) & 0xfffffff, 0.5, 0.7)
|
||||
bg = (255, 255, 255)
|
||||
fg = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
|
||||
|
||||
# print fg, bg
|
||||
draw.rectangle([(0, 0), (size, size)], fill=bg)
|
||||
|
||||
for i in range(15):
|
||||
c = bg if int(h[i], 16) % 2 == 1 else fg
|
||||
if i < 5:
|
||||
draw.rectangle([(2*cell + margin, i*cell + margin), (3*cell + margin, (i+1)*cell + margin)], fill=c)
|
||||
elif i < 10:
|
||||
draw.rectangle([(1*cell + margin, (i-5)*cell + margin), (2*cell + margin, (i-4)*cell + margin)], fill=c)
|
||||
draw.rectangle([(3*cell + margin, (i-5)*cell + margin), (4*cell + margin, (i-4)*cell + margin)], fill=c)
|
||||
elif i < 15:
|
||||
draw.rectangle([(0*cell + margin, (i-10)*cell + margin), (1*cell + margin, (i-9)*cell + margin)], fill=c)
|
||||
draw.rectangle([(4*cell + margin, (i-10)*cell + margin), (5*cell + margin, (i-9)*cell + margin)], fill=c)
|
||||
|
||||
image.save(open("pfp/%s.png" % filename, "w"), "PNG")
|
||||
return
|
|
@ -24,4 +24,8 @@ body {
|
|||
|
||||
.NO_HOVER_UNDERLINE_DAMMIT:hover, .NO_HOVER_UNDERLINE_DAMMIT:focus, .NO_HOVER_UNDERLINE_DAMMIT:active {
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
#avatar {
|
||||
border: 1px solid rgb(221, 221, 221);
|
||||
}
|
|
@ -46,7 +46,7 @@ app.config(function($routeProvider, $locationProvider) {
|
|||
})
|
||||
.when("/settings", {
|
||||
templateUrl: "pages/settings.html",
|
||||
controller: "mainController"
|
||||
controller: "settingsController"
|
||||
})
|
||||
.when("/forgot", {
|
||||
templateUrl: "pages/forgot.html",
|
||||
|
@ -188,6 +188,17 @@ app.controller("adminProblemsController", ["$controller", "$scope", "$http", fun
|
|||
});
|
||||
}]);
|
||||
|
||||
app.controller("settingsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
$.get("/api/user/info", {}, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
console.log(result["user"]);
|
||||
$scope.user = result["user"];
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
}]);
|
||||
|
||||
function display_message(containerId, alertType, message, callback) {
|
||||
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>");
|
||||
$("#" + containerId).hide().slideDown("fast", "swing", function() {
|
||||
|
@ -382,3 +393,12 @@ var accept_invitation = function(tid) {
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
var accept_invitation_request = function(uid) {
|
||||
var data = { "uid": uid };
|
||||
api_call("POST", "/api/team/invite/request/accept", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.reload(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<img src="//www.gravatar.com/avatar/?size=512" id="avatar" style="width:100%;max-width:100%;" />
|
||||
<small style="display:block;text-align:right;" ng-show="user['me']==true"><a href="http://en.gravatar.com/emails/" target="_blank">Edit Picture</a></small>
|
||||
<div style="width:100%;max-width:100%;">
|
||||
<img src="/api/user/avatar/{{ user.uid }}" id="avatar" style="width:100%;" />
|
||||
<small style="display:block;text-align:right;" ng-show="user['me']==true"><a href="/settings#profile">Edit Picture</a></small>
|
||||
</div>
|
||||
<h2 style="margin:0px;font-weight:bold;font-size:2em;">{{ user.name }}</h2>
|
||||
<small style="display:block;font-size:1.5em;color:#999;">@{{ user.username }}</small>
|
||||
<hr>
|
||||
|
|
|
@ -18,7 +18,14 @@
|
|||
<h4 class="panel-title">Public Profile</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>Hi.</p>
|
||||
<label>Profile Picture</label>
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<pre>{{ user }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<div class="list-group-item" ng-repeat="member in team['members']">
|
||||
<h4 class="list-group-item-heading" style="display:inline-block;">{{ member['name'] }}</h4>
|
||||
<div class="label label-info" ng-show="member['captain']==true">Owner</div>
|
||||
<p class="list-group-item-text">@{{ member['username'] }}</p>
|
||||
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer" ng-show="team['is_owner']==true">
|
||||
|
@ -75,10 +75,10 @@
|
|||
<h4 class="panel-title">Pending Invitations</h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item" ng-repeat="member in team['pending_invitations']">
|
||||
<div class="list-group-item" ng-repeat="member in team['pending_invitations']" href="/profile/{{ member['username'] }}">
|
||||
<a class="badge" ng-href="javascript:rescind_invitation({{ member['uid'] }});"><i class="fa fa-fw fa-times"></i></a>
|
||||
<h4 class="list-group-item-heading">{{ member['name'] }}</h4>
|
||||
<p class="list-group-item-text">@{{ member['username'] }}</p>
|
||||
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -87,15 +87,26 @@
|
|||
<h4 class="panel-title">Invitation Requests</h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item" ng-repeat="member in team['invitation_requests']">
|
||||
<div class="list-group-item" ng-repeat="member in team['invitation_requests']" href="/profile/{{ member['username'] }}">
|
||||
<a class="badge" ng-href="javascript:accept_invitation_request({{ member['uid'] }});"><i class="fa fa-fw fa-check"></i></a>
|
||||
<h4 class="list-group-item-heading">{{ member['name'] }}</h4>
|
||||
<p class="list-group-item-text">@{{ member['username'] }}</p>
|
||||
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li role="presentation"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="profile">
|
||||
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="activity">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -185,5 +196,11 @@
|
|||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
$("[data-toggle=tooltip]").tooltip();
|
||||
</script>
|
||||
$(document).ready(function() {
|
||||
$("[data-toggle=tooltip]").tooltip();
|
||||
$("ul[role=tablist]").tab();
|
||||
$("a[role=tab]").click(function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
Loading…
Reference in a new issue