invite members to team
This commit is contained in:
parent
f5f74540fd
commit
8de7c95718
6 changed files with 184 additions and 37 deletions
|
@ -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)
|
||||||
|
@ -138,3 +155,15 @@ class LoginTokens(db.Model):
|
||||||
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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -228,3 +241,13 @@ var create_team = function() {
|
||||||
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -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>
|
||||||
<div ng-show="config.navbar['logged_in']==true && config.navbar['has_team']!=true">
|
<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"> <i class="fa fa-fw fa-plus"></i> </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="!(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) {
|
||||||
|
|
Loading…
Reference in a new issue