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 = [ ]
|
||||
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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -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"> <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="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) {
|
||||
|
|
Loading…
Reference in a new issue