Ejemplo n.º 1
0
def user_invite(email, team1_id, user2_email):  # POST
    """Invite another team to join 1 person team (i.e. team1 -inviting -> user2)
    This endpoints allows users to find other team members using email

    #NOTE user_invite is dependent on team_invite

    Peforms checks to see if user2 is a valid user and whether user2's team is also valid
    then uses team_invite to carry out the invite

    Args:
        team1_id: the id of the team that is inviting user2
        user2_email: the email of the invitee

    Return:
        response object
    """

    user = coll("users").find_one({"_id": user2_email})
    if not user:
        return {"message": f"User ({user2_email}) does not exist"}, 404
    team2_id = user["team_id"]
    team2 = coll("teams").find_one({"_id": team2_id})

    if not team2:
        return {"message": f"Team {team2_id} does not exist"}, 404

    if len(team2["members"]) > 1:
        return {"message": f"User ({user2_email}) already on a team with others"}, 409

    return team_invite(email, team1_id, team2_id)
Ejemplo n.º 2
0
def team_complete(email, team_id):
    """reverse team completion status

    change team completion status:
    if it was incomplete, mark it complete
    if it was complete, mark it incomplete.

    Args:
        email: email of any team member

    Return:
        response object
    """
    team = coll("teams").find_one({"_id": team_id})
    if not team:
        return {"message": "Team does not exist"}, 400

    if email not in team["members"]:
        return {"message": f'User not in team "{team_id}"'}, 403

    team_size = len(team["members"])
    if team["complete"] and team_size <= 4:
        coll("teams").update_one({"_id": team_id}, {"$set": {"complete": False}})
        return {"message": "False"}, 200
    else:
        coll("teams").update_one({"_id": team_id}, {"$set": {"complete": True}})
        return {"message": "True"}, 200
Ejemplo n.º 3
0
def user_leave(email, team_id):  # POST
    """the user leaves a team

    Removes the individual from the team

    Args:
        email: the email of the individual that wants to leave the team

    Return:
        response object
    """
    team = coll("teams").find_one({"_id": team_id})
    if not team:
        return {"message": "Team does not exist"}, 400

    if email not in team["members"]:
        return {"message": f'User not in team "{team_id}"'}, 403

    team_size = len(team["members"])
    if team_size == 1:
        # coll("teams").delete_one({"_id": team["_id"]})

        # Maybe also turn this team into complete?
        return {"message": "A team must have one member"}, 400
    else:
        coll("teams").update_one(
            {"_id": team["_id"]},
            {
                "$pull": {
                    "members": email
                },
                "$set": {
                    "complete": False
                },
                "$set": {
                    "meta":
                    aggregate_team_meta([
                        member for member in team["members"] if member != email
                    ], )
                },
            },
        )

    # Reset the User
    coll("users").update_one({"_id": email},
                             {"$set": {
                                 "hasateam": False,
                                 "team_id": ""
                             }})

    return {"message": "Success"}, 200
Ejemplo n.º 4
0
def create_team_profile(team_name, email, team_desc, skills, prizes):
    """Initialize team

    User creating a team

    Args:
        team_name: name of the team
        email: the email of the individual (already in a team) that wants other people to join his team recommendation
        team_desc: team description
        skills: Preferred skills for the team
        prizes: team goal/prize

    Return:
        response object(403:Invalid user, 401:Invalid name, 402:User In a team, 200: Success)
    """

    user = coll("users").find_one({"_id": email})

    if not user:
        return {"message": "User does not exist"}, 404

    if user["hasateam"]:
        return {"message": "User in a team"}, 400

    random_id = shortuuid.ShortUUID().random(length=15)
    coll("users").update_one(
        {"_id": email}, {"$set": {
            "hasateam": True,
            "team_id": random_id
        }})

    # Don't think we need a check but just incase if uuid is not strong enough
    # while True:  # make sure our id is not a repeat
    #     random_id = random.randint(1000, 9999)
    #     if not coll("teams").find_one({"_id": random_id}):
    #         break

    coll("teams").insert({
        "_id": random_id,
        "name": team_name,
        "members": [email],
        "desc": team_desc,
        "skills": skills,
        "prizes": prizes,
        "complete": False,
        "incoming_inv": [],
        "outgoing_inv": [],
        "meta": aggregate_team_meta([email]),
    })
    return {"message": "Team profile successfully created"}, 201
Ejemplo n.º 5
0
def update_team_profile(email, team_id, **kwargs):  # PUT
    """update team information

    returns team information as text in json, accept kwargs: desc, skills, prizes, interested

    Args:
        email: email of a team member
        team_id: id of the team
    Return:
        response object(401:User not in a team, 200: Success)
    """
    team = coll("teams").find_one({"_id": team_id})
    if not team:
        return {"message": "Team does not exist"}, 404

    if email not in team["members"]:
        return {"message": f'User not in team "{team_id}"'}, 403

    coll("teams").update_one({"_id": team_id}, {"$set": kwargs})

    return {"message": "Team profile successfully updated"}, 200
Ejemplo n.º 6
0
def get_user_profiles(args):  # GET
    """Endpoint to get multiple user profiles at once

    Args (Optional):
    1. limit (int) - number of user profiles to return. Default value = 0 which will return everything
    2. hasateam (bool) - returns user that are in a team or not Default value = none which returns both

    Returns a list of users
    """
    limit = args.get("limit", type=int) if args.get("limit") else 0
    # NOTE checks if the string value of hasateam is equal to "true" because HTTP protocol only passes strings
    hasateam = args.get("hasateam", "").lower() == "true"

    if hasateam:
        users = list(coll("users").find({"hasateam": hasateam}).limit(limit))
    else:
        users = list(coll("users").find({}).limit(limit))

    for user in users:
        user["user_id"] = user.pop("_id")
    return {"user_profiles": users}, 200
Ejemplo n.º 7
0
def team_reject(email, team1_id, team2_id):  # POST
    """rejecting an invite (i.e. team1 -reject-> team2 (rejecting an invite)

    Removes team2 from team1's incoming_inv list and removes team1 from team2's outgoing_inv list

    Args:
        team1_id: id of the team that is doing the reject
        team2_id: id of the team that sent the invite

    Return:
        response object
    """
    team1 = coll("teams").find_one({"_id": team1_id})
    team2 = coll("teams").find_one({"_id": team2_id})

    if not team1 or not team2:
        return {"message": "Invalid team id(s)"}, 404

    if email not in team1["members"]:
        return {"message": f"User not in team {team1_id}"}, 403

    if team1_id not in team2["outgoing_inv"] or team2_id not in team1[
            "incoming_inv"]:
        return {"message": "No invite to reject"}, 404

    coll("teams").update_one({"_id": team1_id},
                             {"$pull": {
                                 "incoming_inv": team2_id
                             }})
    coll("teams").update_one({"_id": team2_id},
                             {"$pull": {
                                 "outgoing_inv": team1_id
                             }})
    return {"message": "Success"}, 200
Ejemplo n.º 8
0
def update_user_profile(email, **kwargs):  # PUT
    """Update user profile

    Update a user profile with new parameters.

    Args:
        1. user's email (str)
        2. skills (list of str) - optional
        3. prizes (list of str) - optional
        4. bio (str) - optional
        5. github (str) - optional

    Returns:
        Status of update (dict)
    """
    user = coll("users").find_one({"_id": email})
    if not user:
        return {"message": "User not found"}, 404

    coll("users").update_one({"_id": email}, {"$set": kwargs})

    return {"message": "User profile successfully updated"}, 200
Ejemplo n.º 9
0
def create_user_profile(email, **kwargs):  # POST
    """Create user profile

    Creates a new user profile from the user email, skills, prizes, and other fields.

    Args:
        1. User's email (str)
        2. skills (list of str) - optional
        3. prizes (list of str) - optional
        4. bio (str) - optional
        5. github (str) - optional
        6. interests (list of str) - optional (AR/VR, BlockChain, Communications, CyberSecurity,
            DevOps, Fintech, Gaming, Healthcare, IoT, LifeHacks, ML/AI, Music, Productivity,
            Social Good, Voice Skills)
        7. seriousness (enum {i.e. int - 1-5}) - optional | default value is 3 (neutral)

    Returns:
        User profile object (dict)
    """
    user_exists = coll("users").find_one({"_id": email})

    if user_exists:
        return {"message": "User already exists"}, 400

    # NOTE Doesn't make sense for a person to have prizes only a team should have this
    coll("users").insert_one(
        {
            "_id": email,
            "skills": kwargs["skills"],
            "prizes": kwargs["prizes"],
            "bio": kwargs["bio"],
            "github": kwargs["github"],
            "interests": kwargs["interests"],
            "seriousness": kwargs["seriousness"],
            "team_id": "",
            "hasateam": False,
        }
    )
    return {"message": "User profile successfully created"}, 201
Ejemplo n.º 10
0
def format_team_object(team):
    team["team_id"] = team.pop("_id")

    # Change structure of members list in team response
    members = []
    for member_email in team["members"]:
        user = coll("users").find_one({"_id": member_email})
        partial_user = {
            "user_id": user["_id"],
            "bio": user["bio"],
            "seriousness": user["seriousness"]
        }
        members.append(partial_user)

    team["members"] = members

    return team
Ejemplo n.º 11
0
def aggregate_team_meta(members):
    team_members = coll("users").find({"_id": {"$in": members}})
    skills, prizes, interests = set(), set(), set()
    seriousness = 0

    for member in team_members:
        skills.update(member["skills"])
        prizes.update(member["prizes"])
        interests.update(member["interests"])
        seriousness += member["seriousness"]
    seriousness /= len(members)
    return {
        "skills": list(skills),
        "prizes": list(prizes),
        "interests": list(interests),
        "seriousness": seriousness,
    }
Ejemplo n.º 12
0
def get_team_profile(email, team_id):  # GET
    """Get team profile

    Fetches from the teams collection by using the team name as key.

    Args:
        User's email (str)
        Team name (str)

    Returns:
        Team profile object (dict)
    """
    team = coll("teams").find_one({"_id": team_id}, {"meta": False})
    if not team:
        return {"message": "Team does not exist"}, 400

    return format_team_object(team), 200
Ejemplo n.º 13
0
def get_user_profile(email):  # GET
    """Get user profile

    Fetches from the user collection by using the user's email as key.

    Args:
        User's email (str)

    Returns:
        User profile object (dict)
    """
    # NOTE: This method previously called LCS with director credentials in order to retrieve the user's name
    # We will update TeamRU to store names along with our user objects, saving the need to call LCS again
    user_profile = coll("users").find_one({"_id": email})
    if not user_profile:
        return {"message": "User not found"}, 404
    user_profile["user_id"] = user_profile.pop("_id")
    return user_profile, 200
Ejemplo n.º 14
0
def team_invite(email, team1_id, team2_id):  # POST
    """Invite another team to join your team (i.e. team1 -inviting-> team2)

    Performs checks to see if these two team can merge then adds team2 to team1's outgoing_inv
    list and adds team1 to team2's incoming_inv list

    Args:
        team1_id: the id of the team that is interested in team2 (inviter)
        team2_id: the id of the team that catches the interest of team1 (invitee)

    Return:
        response object
    """
    team1 = coll("teams").find_one({"_id": team1_id})
    team2 = coll("teams").find_one({"_id": team2_id})

    if not team1 or not team2:
        return {"message": "Invalid team id(s)"}, 404

    if email not in team1["members"]:
        return {"message": f"User not in team {team1_id}"}, 403

    if team1_id == team2_id:  # check to see if you are sending invite to yourself
        return {"message": "Cannot invite your own team"}, 400

    if (
            team1_id in team2["incoming_inv"]
    ):  # (team2_id in team1["outgoing_inv"] will also work) check to see if you are sending a duplicate invite
        return {"message": "Cannot have duplicate invite"}, 400

    if len(team1["members"]) + len(team2["members"]) > 4:
        return {"message": "Team size will be greater than 4"}, 409

    if team1["complete"] or team2["complete"]:
        return {"message": "Team already complete "}, 409

    coll("teams").update_one({"_id": team1_id},
                             {"$push": {
                                 "outgoing_inv": team2_id
                             }})
    coll("teams").update_one({"_id": team2_id},
                             {"$push": {
                                 "incoming_inv": team1_id
                             }})
    # send_email(
    #     to=team2["members"],
    #     subject="Pending TeamRU Invite",
    #     body="Go to https://hackru.org/ to accept the invite",
    # )
    return {"message": "Success"}, 200
Ejemplo n.º 15
0
def get_team_profiles(email, search, offset, limit):
    """Find teams that are open for new members

    Give a list of teams that fulfills the requirement and also still open for new members,
    if search is empty, returns all open teams (according to offset/limit).

    Note: Along with .skip() and .limit() for the offset and limit parameters, we also need
    to use .sort() because the order of records returned by a MongoDB cursor isn't guaranteed
    to be the same every time. We are sorting by ascending _id, which is random (shortUUID).

    Args:
        search: json file filter for complete, desc, skills and prizes
        offset: the number of teams to skip before selecting teams for the response (default 0)
        limit: the number of teams to return in the response (default 10)

    Return:
        list of open teams that pass the filter.
    """
    user = coll("users").find_one({"_id": email})
    team = coll("teams").find_one({"_id": user["team_id"]}, {"meta": False})
    do_not_show = set()
    do_not_show.add(team["_id"])
    do_not_show.update(team["outgoing_inv"])
    do_not_show.update(team["incoming_inv"])
    total_teams = coll("teams").find({
        "complete": False,
        "_id": {
            "$ne": team["_id"]
        }
    }).count()

    all_open_teams = []
    if search is None:
        available_teams = (coll("teams").find(
            {
                "complete": False,
                "_id": {
                    "$ne": team["_id"]
                }
            }, {
                "meta": False
            }).sort("_id", 1).skip(offset).limit(limit))
    else:
        search = search.strip().lower()
        available_teams = (coll("teams").find(
            {
                "complete":
                False,
                "_id": {
                    "$ne": team["_id"]
                },
                "$or": [
                    {
                        "desc": {
                            "$regex": ".*" + search + ".*"
                        }
                    },
                    {
                        "skills": {
                            "$regex": ".*" + search + ".*"
                        }
                    },
                    {
                        "prizes": {
                            "$regex": ".*" + search + ".*"
                        }
                    },
                ],
            },
            {
                "meta": False
            },
        ).sort("_id", 1).skip(offset).limit(limit))

    for team in available_teams:
        team["invited"] = team["_id"] in do_not_show
        all_open_teams.append(format_team_object(team))

    if not all_open_teams:
        return {"message": "No open teams", "total_teams": total_teams}, 400
    return {"all_open_teams": all_open_teams, "total_teams": total_teams}, 200
Ejemplo n.º 16
0
def get_team_recommendations(email):  # GET
    """Finds recommendations of teams for this individual to join

    The current matching algorithms finds teams that are not full already by matching on prizes and skills.

    Args:
        email: the email of the individual that wants recommendations of teams to join

    Return:
        a list of recommended teams to join
    """

    user = coll("users").find_one({"_id": email})
    if not user:
        return {"message": "Invalid user"}, 403

    # basic info about users
    skills = user["skills"]
    interests = user["interests"]
    prizes = user["prizes"]
    seriousness = user["seriousness"]

    names = set()
    matches = []

    # match for skill
    needed_skills = set()
    frontend_languages = set(["html", "css", "javascript", "php", "typscript"])
    backend_languages = set(
        ["java", "php", "ruby", "python", "c", "c++", "sql", "node.js"])
    # judging if the user if frontend or backend, and give backend suggestions if only know frontend, vice versa
    skill_set = set(skills)
    front_num = len(skill_set.intersection(skills))
    back_num = len(skill_set.intersection(skills))

    if front_num > (back_num * len(frontend_languages) /
                    len(backend_languages)):
        if back_num < 3:
            needed_skills.update(backend_languages)
    else:
        if front_num < 3:
            needed_skills.update(frontend_languages)
    if len(needed_skills):
        needed_skills.update(backend_languages)
        needed_skills.update(frontend_languages)

    for skill in needed_skills:
        # collection of all the team's skills
        complementary_skills_match = coll("teams").aggregate([{
            "$match": {
                "complete": False,
                "skills": {
                    "$all": [skill]
                }
            }
        }])
        # collections of all the team's interests
        if not complementary_skills_match:
            continue
        for match in complementary_skills_match:
            if match['_id'] not in names:
                names.add(match['_id'])
                matches.append(match)

    # add base on interests
    # AR/VR, BlockChain, Communications, CyberSecurity, DevOps, Fintech, Gaming,
    # Healthcare, IoT, LifeHacks, ML/AI, Music, Productivity, Social Good, Voice Skills

    # finding team with listed interests, if too much matches, find from teams in the matches
    if len(matches) > 50:
        for match in matches:
            if len(matches) <= 50:
                break
            team_interests = match["meta"]["interests"]
            # team has no common skill
            if len(list(set(interests).intersection(
                    set(team_interests)))) == 0:
                matches.remove(match)
                names.remove(match["_id"])
    else:
        for interest in interests:
            match = coll("teams").aggregate([{
                "$match": {
                    "complete": False,
                    "meta.interest": {
                        "$all": [interest]
                    }
                }
            }])
            if not match:
                continue
            for m in match:
                if m["_id"] not in names:
                    names.add(m["_id"])
                    matches.append(m)

    # add suggestions base on prize
    for prize in prizes:
        match = coll("teams").aggregate([{
            "$match": {
                "complete": False,
                "prizes": {
                    "$all": [prize]
                }
            }
        }])
        if not match:
            continue
        for m in match:
            if m["_id"] not in names:
                names.add(m["_id"])
                matches.append(m)

    # if there are too many matches, reduce it base on seriousness
    if len(matches) > 20:
        for team in matches:
            if (abs(team["seriousness"] - seriousness)) > 2:
                matches.remove(team)
                names.remove(team["_id"])

    # current_team = coll("teams").find_one({"_id": user["team_id"]})
    # try:
    #     matches.remove(current_team)
    # except ValueError:
    #     pass

    # inv_in = current_team["incoming_inv"]
    # inv_out = current_team["outgoing_inv"]

    # inv_sum = set()
    # inv_sum.update(set(inv_in))
    # inv_sum.update(set(inv_out))

    # for i in inv_sum:
    #     try:
    #         matches.remove(i)
    #     except ValueError:
    #         pass

    bad_match_ids = set()
    bad_match_ids.add(user["team_id"])
    current_team = coll("teams").find_one({"_id": user["team_id"]})
    bad_match_ids.update(current_team["incoming_inv"])
    bad_match_ids.update(current_team["outgoing_inv"])
    good_matches = []
    for team in matches:
        if team["_id"] not in bad_match_ids:
            good_matches.append(team)
    matches = good_matches

    if not matches:
        return {"message": "No recommendations found"}, 404

    for team in matches:
        del team["meta"]

    return {"matches": [format_team_object(team) for team in matches]}, 200
Ejemplo n.º 17
0
def team_confirm(email, team1_id, team2_id):  # if request.method == 'POST'
    """Confirming an invitation from a team (i.e. team1 -confirms-> team2)

    team1 is the team which is receiving and confirming the invite
    team2 is the team which made the original invite

    Performs checks to see if the merging can be carried out still (checks include: 1) new team
    size is not > 4 2) no rescinds or rejects beforehand which would have nullified the invite)

    If checks pass then team1 (confirming team) is merged with team2 (original team) by adding
    team1's member(s) into team2's member list and setting team2 to complete if the new team size is 4

    Args:
        team1_id: id of the team that is confirming the invite (invitee)
        team2_id: id of the team that sent the invite (inviter)

    Return:
         response object
    """
    team1 = coll("teams").find_one({"_id": team1_id})
    team2 = coll("teams").find_one({"_id": team2_id})

    if not team1 or not team2:
        return {"message": "Invalid team id(s)"}, 404

    if email not in team1["members"]:
        return {"message": f"User not in team {team1_id}"}, 403

    new_length = len(team1["members"]) + len(team2["members"])
    if new_length > 4:
        return {"message": "Team size will be greater than 4"}, 409

    if team1["complete"] or team2["complete"]:
        return {"message": "Team complete"}, 409

    if team1_id not in team2["outgoing_inv"] and team2_id not in team1[
            "incoming_inv"]:
        return {"message": "Invite no longer exists"}, 404

    # NOTE So we can do merging of the two teams (documents) however we want (this is just an example)
    # currently the other team is being deleted but we should really archive it for futuring training
    # purposes for our ML model
    coll("teams").update_one(
        {"_id": team2_id},
        {
            "$push": {
                "members": {
                    "$each": team1["members"]
                }
            },
            "$pull": {
                "outgoing_inv": team1_id
            },
            "$set": {
                "complete": new_length == 4,
                "meta":
                aggregate_team_meta(team1["members"] + team2["members"]),
            },
        },
    )
    coll("users").update({"_id": {
        "$in": team1["members"]
    }}, {"$set": {
        "team_id": team2_id
    }})

    doc = coll("teams").find_one_and_delete({"_id": team1_id})
    coll("archive").insert_one(doc)

    coll("teams").update(
        {}, {"$pull": {
            "outgoing_inv": team1_id,
            "incoming_inv": team1_id
        }},
        multi=True
    )  # removes team1_id from all the remaining teams' outgoing and incoming invites
    if (
            new_length == 4
    ):  # removes team2_id from all the remaining team because team2 is full now
        coll("teams").update(
            {},
            {"$pull": {
                "outgoing_inv": team2_id,
                "incoming_inv": team2_id
            }},
            multi=True,
        )

    # NOTE  At the end of HackRU we can perform a backup job which will archive all the successfully created team

    return {"message": "Success"}, 200