invite members to team

This commit is contained in:
Michael Zhang 2016-01-16 15:12:32 -06:00
parent f5f74540fd
commit 8de7c95718
6 changed files with 184 additions and 37 deletions

View file

@ -48,7 +48,9 @@ class Teams(db.Model):
members = [ ]
for member in Users.query.filter_by(tid=self.tid).all():
members.append({
"username": member.username
"username": member.username,
"name": member.name,
"captain": member.uid == self.owner
})
return members
@ -71,6 +73,25 @@ class Teams(db.Model):
except ValueError:
return (-1, "--")
def get_pending_invitations(self, toid=None):
if toid is not None:
invitation = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid, toid=toid).first()
if invitation is None:
return None
else:
user = db.session.query(Users).filter_by(uid=invitation.toid).first()
return { "username": user.username, "name": user.name, "uid": user.uid }
result = [ ]
invitations = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid).all()
for invitation in invitations:
user = db.session.query(Users).filter_by(uid=invitation.toid).first()
result.append({
"username": user.username,
"name": user.name,
"uid": user.uid
})
return result
class Problems(db.Model):
pid = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
@ -115,10 +136,6 @@ class Solves(db.Model):
self.flag = flag
self.correct = correct
##########
# TOKENS #
##########
class LoginTokens(db.Model):
sid = db.Column(db.String(64), unique=True, primary_key=True)
uid = db.Column(db.Integer)
@ -137,4 +154,16 @@ class LoginTokens(db.Model):
self.expiry = expiry
self.active = active
self.ua = ua
self.ip = ip
self.ip = ip
class TeamInvitations(db.Model):
rid = db.Column(db.Integer, primary_key=True)
rtype = db.Column(db.Integer)
frid = db.Column(db.Integer)
toid = db.Column(db.Integer)
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
def __init__(self, rtype, frid, toid):
self.rtype = rtype
self.frid = frid
self.toid = toid

View file

@ -2,7 +2,7 @@ from flask import Blueprint, request
from flask import current_app as app
from voluptuous import Schema, Length, Required
from models import db, Teams, Users
from models import db, Teams, Users, TeamInvitations
from decorators import api_wrapper, login_required, WebException
from schemas import verify_to_schema, check
@ -37,31 +37,64 @@ def team_create():
return { "success": 1, "message": "Success!" }
@blueprint.route("/invite", methods=["POST"])
@api_wrapper
@login_required
def team_invite():
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()
if _user.uid != _team.owner:
raise WebException("You must be the captain of your team to invite members!")
new_member = params.get("new_member")
if new_member is None:
raise WebException("Please provide a username!")
_user2 = user.get_user(username_lower=new_member.lower()).first()
if _user2 is None:
raise WebException("User doesn't exist!")
if _user2.tid > 0:
raise WebException("This user is already a part of a team!")
if _team.get_pending_invitations(toid=_user2.uid) is not None:
raise WebException("You've already invited this member!")
req = TeamInvitations(0, _team.tid, _user2.uid)
with app.app_context():
db.session.add(req)
db.session.commit()
return { "success": 1, "message": "Success!" }
@blueprint.route("/info", methods=["GET"])
@api_wrapper
def team_info():
logged_in = user.is_logged_in()
me = False
in_team = False
owner = False
_user = None
search = { }
teamname = utils.flat_multi(request.args).get("teamname")
if teamname:
search.update({ "teamname_lower": teamname.lower() })
if logged_in:
my_team = get_team().first()
if my_team is not None:
if teamname is None:
teamname = my_team.teamname
me = True
elif teamname.lower() == my_team.teamname.lower():
me = True
if teamname is None:
raise WebException("No team specified.")
team = get_team(teamname_lower=teamname.lower()).first()
if team is None:
raise WebException("Team not found.")
teamdata = {
"teamname": team.teamname,
"school": team.school
}
teamdata["in_team"] = me
_user = user.get_user().first()
if user.in_team(_user):
if "teamname_lower" not in search:
search.update({ "tid": _user.tid })
in_team = True
team = get_team(**search).first()
teamdata = get_team_info(**search)
if logged_in:
in_team = teamdata["tid"] == _user.tid
owner = teamdata["captain"] == _user.uid
teamdata["in_team"] = in_team
if in_team:
teamdata["is_owner"] = owner
if owner:
teamdata["pending_invitations"] = team.get_pending_invitations()
return { "success": 1, "team": teamdata }
##################
@ -84,6 +117,9 @@ TeamSchema = Schema({
def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None):
team = get_team(tid=tid, teamname=teamname, teamname_lower=teamname_lower, owner=owner).first()
if team is None:
raise WebException("Team not found.")
place_number, place = team.place()
result = {
"tid": team.tid,
@ -91,7 +127,9 @@ def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None):
"school": team.school,
"place": place,
"place_number": place_number,
"points": team.points()
"points": team.points(),
"members": team.get_members(),
"captain": team.owner,
}
return result

View file

@ -137,7 +137,7 @@ UserSchema = Schema({
),
Required("username"): check(
([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."),
([utils.__check_ascii], "Please only use ASCII characters in your username."),
([utils.__check_alphanumeric], "Please only use alphanumeric characters in your username."),
([__check_username], "This username is taken, did you forget your password?")
),
Required("password"): check(

View file

@ -13,6 +13,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
__check_email_format = lambda email: re.match(".+@.+\..{2,}", email) is not None
__check_ascii = lambda s: all(c in string.printable for c in s)
__check_alphanumeric = lambda s: all(c in string.digits + string.ascii_uppercase + string.ascii_lowercase for c in s)
def hash_password(s):
return generate_password_hash(s)

View file

@ -1,4 +1,8 @@
var app = angular.module("easyctf", [ "ngRoute" ]);
app.config(["$compileProvider", function($compileProvider) {
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|javascript):/);
}]);
app.config(function($routeProvider, $locationProvider) {
$routeProvider.when("/", {
templateUrl: "pages/home.html",
@ -158,6 +162,18 @@ function display_message(containerId, alertType, message, callback) {
});
};
function api_call(method, url, data, callback) {
if (method.toLowerCase() == "post") {
data["csrf_token"] = $.cookie("csrf_token");
}
$.ajax({
"type": method,
"datatype": "json",
"data": data,
"url": url
}).done(callback);
}
$.fn.serializeObject = function() {
var a, o;
o = {};
@ -180,8 +196,7 @@ $.fn.serializeObject = function() {
var register_form = function() {
var input = "#register_form input";
var data = $("#register_form").serializeObject();
data["csrf_token"] = $.cookie("csrf_token");
$.post("/api/user/register", data, function(result) {
api_call("POST", "/api/user/register", data, function(result) {
if (result["success"] == 1) {
location.href = "/profile";
} else {
@ -198,8 +213,7 @@ var register_form = function() {
var login_form = function() {
var input = "#login_form input";
var data = $("#login_form").serializeObject();
data["csrf_token"] = $.cookie("csrf_token");
$.post("/api/user/login", data, function(result) {
api_call("POST", "/api/user/login", data, function(result) {
if (result["success"] == 1) {
location.href = "/profile";
} else {
@ -216,8 +230,7 @@ var login_form = function() {
var create_team = function() {
var input = "#create_team input";
var data = $("#create_team").serializeObject();
data["csrf_token"] = $.cookie("csrf_token");
$.post("/api/team/create", data, function(result) {
api_call("POST", "/api/team/create", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
} else {
@ -227,4 +240,14 @@ var create_team = function() {
var result = JSON.parse(jqXHR["responseText"]);
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]);
});
};
var add_member = function() {
var input = "#add_member input";
var data = $("#add_member").serializeObject();
api_call("POST", "/api/team/invite", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
}
});
};

View file

@ -16,7 +16,7 @@
}
</style>
<div ng-show="!(config.navbar['logged_in']==true && config.navbar['has_team']!=true)">
<div ng-show="team['tid'] >= 0">
<div class="jumbotron">
<center>
<div ng-show="team['in_team']==true">
@ -33,8 +33,58 @@
</div>
</center>
</div>
<div class="row">
<div class="col-sm-3 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Team Members</h4>
</div>
<div class="list-group">
<div class="list-group-item" ng-repeat="member in team['members']">
<h4 class="list-group-item-heading">{{ member['name'] }}</h4>
<p class="list-group-item-text">@{{ member['username'] }}</p>
</div>
</div>
<div class="panel-footer" ng-show="team['is_owner']==true">
<form id="add_member" onsubmit="add_member(); return false;" style="margin:none;">
<div class="row">
<div class="col-xs-12">
<div class="input-group">
<input type="text" class="form-control" id="new_member" name="new_member" placeholder="Add member..." autocomplete="off">
<span class="input-group-btn">
<button class="btn btn-success" type="submit">&nbsp;<i class="fa fa-fw fa-plus"></i>&nbsp;</button>
</span>
</div>
</div>
</div>
</form>
</div>
<div class="panel-footer" ng-show="team['in_team']!=true && config.navbar['logged_in']==true">
<div class="row">
<div class="col-xs-12">
<a href="javascript:request_invitation();" class="btn btn-primary col-xs-12"><i class="fa fa-fw fa-plus"></i> Join this team</a>
</div>
</div>
</div>
</div>
<div class="panel panel-default" ng-show="team['is_owner']==true && team['pending_invitations'].length > 0">
<div class="panel-heading">
<h4 class="panel-title">Pending Invitations</h4>
</div>
<div class="list-group">
<div class="list-group-item" ng-repeat="member in team['pending_invitations']">
<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>
</div>
</div>
</div>
</div>
<div class="col-sm-9 col-xs-12">
</div>
</div>
</div>
<div ng-show="config.navbar['logged_in']==true && config.navbar['has_team']!=true">
<div ng-show="!(team['tid'] >= 0) && config.navbar['logged_in']==true">
<div class="page-header">
<h1>Team</h1>
</div>
@ -75,7 +125,6 @@
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Create Team" />
</center>
</div>
@ -93,6 +142,13 @@
</div>
<p>You need an invitation to join another team. If you'd like to request to be a member of their team, go to their team page and click the Request button.</p>
</div>
<div ng-show="!(team['tid'] >= 0) && config.navbar['logged_in']!=true">
<div class="page-header">
<h1>Team Not Found</h1>
</div>
<p>The team you were looking for doesn't exist. Check to make sure you've spelled the name right.</p>
</div>
<script type="text/javascript">
$("#teamname_edit").on("keypress", function(e) {