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 = [ ] members = [ ]
for member in Users.query.filter_by(tid=self.tid).all(): for member in Users.query.filter_by(tid=self.tid).all():
members.append({ members.append({
"username": member.username "username": member.username,
"name": member.name,
"captain": member.uid == self.owner
}) })
return members return members
@ -71,6 +73,25 @@ class Teams(db.Model):
except ValueError: except ValueError:
return (-1, "--") 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): class Problems(db.Model):
pid = db.Column(db.Integer, primary_key=True) pid = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128)) name = db.Column(db.String(128))
@ -115,10 +136,6 @@ class Solves(db.Model):
self.flag = flag self.flag = flag
self.correct = correct self.correct = correct
##########
# TOKENS #
##########
class LoginTokens(db.Model): class LoginTokens(db.Model):
sid = db.Column(db.String(64), unique=True, primary_key=True) sid = db.Column(db.String(64), unique=True, primary_key=True)
uid = db.Column(db.Integer) uid = db.Column(db.Integer)
@ -137,4 +154,16 @@ class LoginTokens(db.Model):
self.expiry = expiry self.expiry = expiry
self.active = active self.active = active
self.ua = ua 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 flask import current_app as app
from voluptuous import Schema, Length, Required 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 decorators import api_wrapper, login_required, WebException
from schemas import verify_to_schema, check from schemas import verify_to_schema, check
@ -37,31 +37,64 @@ def team_create():
return { "success": 1, "message": "Success!" } 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"]) @blueprint.route("/info", methods=["GET"])
@api_wrapper @api_wrapper
def team_info(): def team_info():
logged_in = user.is_logged_in() logged_in = user.is_logged_in()
me = False in_team = False
owner = False
_user = None
search = { }
teamname = utils.flat_multi(request.args).get("teamname") teamname = utils.flat_multi(request.args).get("teamname")
if teamname:
search.update({ "teamname_lower": teamname.lower() })
if logged_in: if logged_in:
my_team = get_team().first() _user = user.get_user().first()
if my_team is not None: if user.in_team(_user):
if teamname is None: if "teamname_lower" not in search:
teamname = my_team.teamname search.update({ "tid": _user.tid })
me = True in_team = True
elif teamname.lower() == my_team.teamname.lower(): team = get_team(**search).first()
me = True teamdata = get_team_info(**search)
if teamname is None: if logged_in:
raise WebException("No team specified.") in_team = teamdata["tid"] == _user.tid
team = get_team(teamname_lower=teamname.lower()).first() owner = teamdata["captain"] == _user.uid
if team is None: teamdata["in_team"] = in_team
raise WebException("Team not found.") if in_team:
teamdata["is_owner"] = owner
teamdata = { if owner:
"teamname": team.teamname, teamdata["pending_invitations"] = team.get_pending_invitations()
"school": team.school
}
teamdata["in_team"] = me
return { "success": 1, "team": teamdata } return { "success": 1, "team": teamdata }
################## ##################
@ -84,6 +117,9 @@ TeamSchema = Schema({
def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None): 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() 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() place_number, place = team.place()
result = { result = {
"tid": team.tid, "tid": team.tid,
@ -91,7 +127,9 @@ def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None):
"school": team.school, "school": team.school,
"place": place, "place": place,
"place_number": place_number, "place_number": place_number,
"points": team.points() "points": team.points(),
"members": team.get_members(),
"captain": team.owner,
} }
return result return result

View file

@ -137,7 +137,7 @@ UserSchema = Schema({
), ),
Required("username"): check( Required("username"): check(
([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."), ([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?") ([__check_username], "This username is taken, did you forget your password?")
), ),
Required("password"): check( 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_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_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): def hash_password(s):
return generate_password_hash(s) return generate_password_hash(s)

View file

@ -1,4 +1,8 @@
var app = angular.module("easyctf", [ "ngRoute" ]); var app = angular.module("easyctf", [ "ngRoute" ]);
app.config(["$compileProvider", function($compileProvider) {
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|javascript):/);
}]);
app.config(function($routeProvider, $locationProvider) { app.config(function($routeProvider, $locationProvider) {
$routeProvider.when("/", { $routeProvider.when("/", {
templateUrl: "pages/home.html", 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() { $.fn.serializeObject = function() {
var a, o; var a, o;
o = {}; o = {};
@ -180,8 +196,7 @@ $.fn.serializeObject = function() {
var register_form = function() { var register_form = function() {
var input = "#register_form input"; var input = "#register_form input";
var data = $("#register_form").serializeObject(); var data = $("#register_form").serializeObject();
data["csrf_token"] = $.cookie("csrf_token"); api_call("POST", "/api/user/register", data, function(result) {
$.post("/api/user/register", data, function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
location.href = "/profile"; location.href = "/profile";
} else { } else {
@ -198,8 +213,7 @@ var register_form = function() {
var login_form = function() { var login_form = function() {
var input = "#login_form input"; var input = "#login_form input";
var data = $("#login_form").serializeObject(); var data = $("#login_form").serializeObject();
data["csrf_token"] = $.cookie("csrf_token"); api_call("POST", "/api/user/login", data, function(result) {
$.post("/api/user/login", data, function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
location.href = "/profile"; location.href = "/profile";
} else { } else {
@ -216,8 +230,7 @@ var login_form = function() {
var create_team = function() { var create_team = function() {
var input = "#create_team input"; var input = "#create_team input";
var data = $("#create_team").serializeObject(); var data = $("#create_team").serializeObject();
data["csrf_token"] = $.cookie("csrf_token"); api_call("POST", "/api/team/create", data, function(result) {
$.post("/api/team/create", data, function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
location.reload(true); location.reload(true);
} else { } else {
@ -227,4 +240,14 @@ var create_team = function() {
var result = JSON.parse(jqXHR["responseText"]); var result = JSON.parse(jqXHR["responseText"]);
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]); 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> </style>
<div ng-show="!(config.navbar['logged_in']==true && config.navbar['has_team']!=true)"> <div ng-show="team['tid'] >= 0">
<div class="jumbotron"> <div class="jumbotron">
<center> <center>
<div ng-show="team['in_team']==true"> <div ng-show="team['in_team']==true">
@ -33,8 +33,58 @@
</div> </div>
</center> </center>
</div> </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>
<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"> <div class="page-header">
<h1>Team</h1> <h1>Team</h1>
</div> </div>
@ -75,7 +125,6 @@
<div class="row"> <div class="row">
<div class="col-sm-12 form-group"> <div class="col-sm-12 form-group">
<center> <center>
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Create Team" /> <input type="submit" class="btn btn-success btn-lg" value="Create Team" />
</center> </center>
</div> </div>
@ -93,6 +142,13 @@
</div> </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> <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>
<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"> <script type="text/javascript">
$("#teamname_edit").on("keypress", function(e) { $("#teamname_edit").on("keypress", function(e) {