Exemple #1
0
def get_snacks() -> dict:
    """
    combines snacks from snack API with vote numbers. Sorts into map
    of three lists:
        - permanent: snacks that are always ordered
        - suggestedCurrent: snacks that are suggested for the current month
        - suggestedExpired: snacks whose suggestions have expired

    Raises:
    - ApiNotAvailableException if snacks API is down
    - AuthorizationError if request to snacks API fails
    """
    snax: dict = get_snacks_remote()
    snacks_permanent: list = []
    snacks_suggested_valid: list = []
    snacks_suggested_expired: list = []

    final_snacks = {
        "permanent": snacks_permanent,
        "suggestedCurrent": snacks_suggested_valid,
        "suggestedExpired": snacks_suggested_expired
    }

    # collect snack ids to query votes for
    snack_ids: list = snax.keys()
    # collect votes
    session = Session()
    rows = session.query(Vote.snack_id, func.count(Vote.id))\
        .filter(Vote.snack_id.in_(snack_ids))\
        .filter(datetime.datetime.now() < Vote.vote_expiry)\
        .group_by(Vote.snack_id)

    session.close()
    # add votes to snack objects
    for snack_id, count in rows:
        snack = snax[snack_id].to_dict()

        snack["votes"] = count

        if snack["suggestionExpiry"] > datetime.datetime.now():
            snacks_suggested_valid.append(snack)
        else:
            snacks_suggested_expired.append(snack)

        # remove from dictionary so it isn't double added
        del snax[snack_id]

    # add remaining snax (no votes) to final
    for k, v in snax.items():
        if v.optional:
            now = datetime.datetime.now()
            if v.suggestionExpiry and v.suggestionExpiry > now:
                snacks_suggested_valid.append(v.to_dict())
            else:
                snacks_suggested_expired.append(v.to_dict())
        else:
            snacks_permanent.append(v.to_dict())

    return final_snacks
def test_create_user():
    create_user('test_user', 'test_password')

    session = Session()

    test_user = session.query(User).filter(User.username == 'test_user').first()

    assert test_user
Exemple #3
0
def add_vote(user_id: int, snack_id: int) -> int:
    """
    Adds a vote for the snack by the user

    raises:
        - VotesExceededException if the user has exceeded allotted votes
        - UserNotFoundException if the user_id is not found

    returns:
        - vote_count int
    """

    # count votes for the user that haven't expired
    user_votes: int = users.get_user_votes(user_id)

    if user_votes >= max_votes:
        raise VotesExceededException

    # create new vote
    new_vote = Vote(user_id=user_id,
                    snack_id=snack_id,
                    vote_expiry=vote_expiration())

    session = Session()

    session.add(new_vote)
    session.commit()
    session.close()

    return user_votes + 1
Exemple #4
0
def get_user_by_id(user_id: int) -> User:
    """
    Looks up user by id.

    Raises:
        - UserNotFoundException if user id does not exist

    Returns:
        User
    """
    session = Session()

    # verify user_id exists
    vote_user: User = session.query(User).filter(User.id == user_id).first()
    session.close()

    if not vote_user:
        raise UserNotFoundException

    return vote_user
Exemple #5
0
def get_user_votes(user_id: int) -> int:
    """
    Gets the number of votes for the user in the current time period

    Raises:
        - UserNotFoundException if user id does not exist

    Returns:
        - int: count of votes
    """
    session = Session()

    # get user by id to ensure user exists
    get_user_by_id(user_id)
    # count votes for the user that haven't expired
    user_votes: int = session.query(Vote)\
        .filter(Vote.user_id == user_id)\
        .filter(Vote.vote_expiry > datetime.datetime.now()).count()

    session.close()

    return user_votes
Exemple #6
0
def test_vote_expire():
    my_user: User = create_user('test_user', 'test_password')
    vote_count: int = get_user_votes(my_user.id)
    assert vote_count == 0

    # add votes that expired before now
    session = Session()
    vote = Vote(
        user_id = my_user.id,
        snack_id = 234,
        vote_expiry = datetime.datetime.now() - datetime.timedelta(days=1)
    )
    # assert that the vote count is still 0 with expired votes
    vote_count: int = get_user_votes(my_user.id)
    assert vote_count == 0
Exemple #7
0
def set_user_suggestion(user_id: int):
    """
    sets user suggestion expiry to the end of the alotted period

    Raises:
        - UserNotFoundException if user id does not exist

    """
    session = Session()

    user = get_user_by_id(user_id)

    user.suggestion_expiry = properties.vote_expiration()

    session.merge(user)
    session.commit()
    session.close()
Exemple #8
0
def verify_user_password(username: str, password: str) -> bool:
    """
    verifies whether the user password is correct

    raises:
        - UserNotFoundException if user does not exist

    returns:
        - bool of whether pw is valid
    """
    session = Session()
    user: User = session.query(User).filter(User.username == username).first()

    pw_input = password.encode('utf-8')
    if user and bcrypt.checkpw(pw_input, user.password_hash.encode('utf-8')):
        session.refresh(user)
        return user.id

    session.close()

    if not user:
        raise UserNotFoundException

    return False
Exemple #9
0
def create_user(username: str, password: str) -> User:
    """
    adds user to database

    :param username: str username
    :param password: str password
    :return: User
    """
    session = Session()

    hash_pw = bcrypt.hashpw(password.encode('utf-8'),
                            bcrypt.gensalt()).decode('utf-8')
    new_user: User = User(username=username, password_hash=hash_pw)
    # TODO: handle generic sql errors
    try:
        session.add(new_user)
        session.commit()
        # gets id
        session.refresh(new_user)
        session.close()
        return new_user
    except IntegrityError as e:
        session.rollback()
        session.close()
        raise UserAlreadyExistsException
Exemple #10
0
def get_snacks_remote() -> dict:
    """
    Fetches snacks from snack service and adds to snack table
    Returns map of snack_id -> snack

    Raises:
    - ApiNotAvailableException if snacks API is down
    - AuthorizationError if request to snacks API fails
    """
    res: requests.Response = requests.get(
        properties.snacks_api_url,
        headers={
            "Authorization": "ApiKey {}".format(properties.snacks_api_key)
        }
    )
    # check for auth errors
    if res.status_code == 401:
        raise AuthorizationError(
            json.dumps({"error": "Unable to authorize to snacks api"}))

    # check for service being down
    if res.status_code >= 500:
        raise ApiNotAvailableException

    snax: list = res.json()
    final_snacks: dict = {}

    for snack in snax:
        new_snack = Snack(**snack)
        # add snacks to db.
        session = Session()
        new_snack = session.merge(new_snack)
        session.commit()
        session.refresh(new_snack)
        session.close()
        final_snacks[new_snack.id] = new_snack

    session.close()

    return final_snacks
Exemple #11
0
def add_snack(name: str, location: str) -> Snack:
    """
    Sends in a snack suggestion, to snack service
    Adds to snacks table if successful.
    returns the Snack object.
    Note:
        - create snack api supports latitude and longitude parameters but they
        aren't anywhere in the output so I'm not including the input

    Raises:
        - AuthorizationError if request to snacks API fails
        - BadRequestError if the snack already exists

    """
    res: requests.Response = requests.post(
        properties.snacks_api_url,
        headers={
            "Authorization": "ApiKey {}".format(properties.snacks_api_key),
            "Content-Type": "application/json"
        },
        data=json.dumps({
            "name": name,
            "location": location
        })
    )
    # check for auth errors
    if res.status_code == 401:
        raise AuthorizationError(
            {"error": "Unable to authorize to snacks api"}
        )

    # check for conflicts
    elif res.status_code == 401:
        raise BadRequestError(res.json())

    # check for service being down
    elif res.status_code >= 500:
        raise ApiNotAvailableException

    # 409 is returned when snack already exists.
    # However, I still want to update
    # the suggestionExpiry if its null or not the current month
    elif res.status_code == 409:
        session = Session()
        currentSnack = session.query(
                Snack
            ).filter(
                Snack.name == name
            ).filter(Snack.suggestionExpiry > datetime.datetime.now()).first()

        # snack exists in db with a valid suggestion month, it has already been
        # suggested
        if currentSnack:
            raise BadRequestError(res.json())

        # snack exists in snack api but does not have a valid suggestion month
        # in db. Add suggestion month to db if optional snack. Otherwise raise
        # error
        else:
            extant_snack = get_snack_remote_by_name(name)
            if extant_snack.optional:
                extant_snack.suggestionExpiry = get_next_month()
                session.merge(extant_snack)
                session.commit()
                return extant_snack
            else:
                raise BadRequestError(
                    "Cannot suggest a snack that is always purchased")

    else:
        # add to db
        session = Session()

        new_snack = Snack(**res.json())
        new_snack.suggestionExpiry = get_next_month()

        session.merge(new_snack)
        session.commit()
        session.close()

        return new_snack