def register_user():
    """Register a new user."""
    try:
        email = request.form['email']
        pw = request.form['password']
    except KeyError:
        raise helpers.BadRequest(errors.MISSING_FIELD,
                                 "missing e-mail and / or password")
    # Check that there is no user with that e-mail address.
    if g.store.find(User, User.email == email).one() is not None:
        raise helpers.BadRequest(errors.EXISTING_USER, "user already exists")
    # Check that the e-mail address is valid.
    elif not mail.is_valid(email):
        raise helpers.BadRequest(errors.INVALID_EMAIL, "e-mail is not valid")
    # Check that the password is good enough.
    elif not password.is_good_enough(pw):
        raise helpers.BadRequest(errors.INVALID_PASSWORD,
                                 "password is not satisfactory")
    # All the checks went through, we can create the user.
    user = User(email, password.encrypt(pw))
    g.store.add(user)
    g.store.flush()  # Necessary to get an ID.
    # Default nickname.
    user.nickname = unicode("user%d" % user.id)
    return jsonify(uid=user.id)
def update_user_group(user, uid):
    """Join or leave a group."""
    helpers.ensure_users_match(user, uid)
    if request.method == 'DELETE':
        if user.group is not None:
            if user.group.master == user:
                user.group.master = None
            event = GroupEvent(user.group, user, events.LEAVE, None)
            g.store.add(event)
        user.group = None
        return helpers.success()
    try:
        gid = int(request.form['gid'])
    except:
        raise helpers.BadRequest(errors.MISSING_FIELD,
                                 "cannot to parse group ID")
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    if user.group != group:
        if user.group is not None:
            if user.group.master == user:
                # The user was his old group's master.
                user.group.master == None
            event = GroupEvent(user.group, user, events.LEAVE, None)
            g.store.add(event)
        user.group = group
        event = GroupEvent(group, user, events.JOIN, None)
        g.store.add(event)
    return helpers.success()
Example #3
0
def update_library(user, uid):
    """Update (add or delete) a user's library."""
    helpers.ensure_users_match(user, uid)
    current_entries = local_valid_entries(user)
    for json_delta in request.form.getlist('delta'):
        try:
            delta = json.loads(json_delta)
            delta_type = delta['type']
            artist = delta['entry']['artist']
            title = delta['entry']['title']
            local_id = int(delta['entry']['local_id'])
        except:
            raise helpers.BadRequest(errors.INVALID_DELTA,
                                     "not a valid library delta")
        key = hashlib.sha1(
            artist.encode('utf-8') + title.encode('utf-8') +
            str(local_id)).digest()
        if delta_type == 'PUT':
            if key not in current_entries:
                set_lib_entry(user, artist, title, local_id=local_id)
        elif delta_type == 'DELETE':
            if key in current_entries:
                current_entries[key].is_valid = False
        else:
            # Unknown delta type.
            raise helpers.BadRequest(errors.INVALID_DELTA,
                                     "not a valid library delta")
    # Update the user's model.
    g.store.flush()
    predict.Model(user).generate(g.store)
    return helpers.success()
Example #4
0
def add_rating(user, gid):
    """Take the DJ spot (if it is available)."""
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    try:
        artist = request.form['artist']
        title = request.form['title']
        rating = max(1, min(5, int(request.form['rating'])))
    except KeyError:
        raise helpers.BadRequest(errors.MISSING_FIELD,
                                 "missing artist, title or rating")
    except ValueError:
        raise helpers.BadRequest(errors.INVALID_RATING, "rating is invalid")
    if user.group != group:
        raise helpers.Unauthorized("you are not in this group")
    track = g.store.find(Track, (Track.artist == artist) &
                         (Track.title == title)).one()
    if track is None:
        raise helpers.BadRequest(errors.INVALID_TRACK, "track not found")
    # Add a group event.
    event = GroupEvent(group, user, events.RATING)
    event.payload = {
        'artist': track.artist,
        'title': track.title,
        'rating': rating,
    }
    g.store.add(event)
    # Add a library entry.
    set_rating(user, track.artist, track.title, rating)
    return helpers.success()
def update_user_password(user, uid):
    """Update the user's password."""
    helpers.ensure_users_match(user, uid)
    try:
        pw = request.form['password']
    except KeyError:
        raise helpers.BadRequest(errors.MISSING_FIELD, "missing password")
    if not password.is_good_enough(pw):
        raise helpers.BadRequest(errors.INVALID_EMAIL,
                                 "password is not satisfactory")
    user.password = password.encrypt(pw)
    return helpers.success()
Example #6
0
def add_rating(user, uid):
    """Set a rating for the user."""
    helpers.ensure_users_match(user, uid)
    try:
        artist = request.form['artist']
        title = request.form['title']
        rating = max(1, min(5, int(request.form['rating'])))
    except KeyError:
        raise helpers.BadRequest(errors.MISSING_FIELD,
                                 "missing artist, title or rating")
    except ValueError:
        raise helpers.BadRequest(errors.INVALID_RATING, "rating is invalid")
    set_rating(user, artist, title, rating)
    return helpers.success()
Example #7
0
def get_playlist(master, gid):
    """Get the playlist id."""
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    id = get_playlist_id(group)
    return jsonify(playlist_id=id)
Example #8
0
def set_rating(user, artist, title, rating):
    """Insert a rating into the database.
    
    Helper function that handles the various cases that can arise, e.g. when a
    track is already in the user's library.
    """
    track = g.store.find(Track, (Track.artist == artist) &
                         (Track.title == title)).one()
    if track is None:
        raise helpers.BadRequest(errors.INVALID_TRACK, "track not found")
    entry = g.store.find(LibEntry, (LibEntry.user == user)
                         & (LibEntry.track == track)
                         & LibEntry.is_valid).one()
    if entry is None:
        # First time we hear about this (user, track) pair.
        set_lib_entry(user, artist, title, rating=rating)
    elif entry.rating is None:
        # User has an entry for this track, but no rating yet.
        entry.rating = rating
    else:
        # Rating already present, we need to create a new entry.
        set_lib_entry(user,
                      artist,
                      title,
                      local_id=entry.local_id,
                      rating=rating)
Example #9
0
def dump_library(user, uid):
    """Dump (create or replace) a user's library."""
    helpers.ensure_users_match(user, uid)
    current_entries = local_valid_entries(user)
    next_entries = set()
    for json_entry in request.form.getlist('entry'):
        try:
            entry = json.loads(json_entry)
            artist = entry['artist']
            title = entry['title']
            local_id = int(entry['local_id'])
        except:
            raise helpers.BadRequest(errors.INVALID_LIBENTRY,
                                     "not a valid library entry")
        key = hashlib.sha1(
            artist.encode('utf-8') + title.encode('utf-8') +
            str(local_id)).digest()
        next_entries.add(key)
        if key not in current_entries:
            set_lib_entry(user, artist, title, local_id=local_id)
    # Invalidate entries that are not in the request.
    for key, entry in current_entries.iteritems():
        if key not in next_entries:
            entry.is_valid = False
    # Update the user's model.
    g.store.flush()
    predict.Model(user).generate(g.store)
    return helpers.success()
Example #10
0
def set_master(user, gid):
    """Take the DJ spot (if it is available)."""
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    try:
        uid = int(request.form['uid'])
    except (KeyError, ValueError):
        raise helpers.BadRequest(errors.MISSING_FIELD, "cannot parse uid")
    if user.id != uid or user.group != group:
        raise helpers.Unauthorized("user not self or not in group")
    if group.master != None and group.master != user:
        raise helpers.Unauthorized("someone else is already here")
    group.master = user
    event = GroupEvent(group, user, events.MASTER, None)
    g.store.add(event)
    return helpers.success()
Example #11
0
def update_user_nickname(user, uid):
    """Assign a nickname to the user."""
    helpers.ensure_users_match(user, uid)
    try:
        user.nickname = request.form['nickname']
    except KeyError:
        raise helpers.BadRequest(errors.MISSING_FIELD, "missing nickname")
    return helpers.success()
Example #12
0
def update_user_email(user, uid):
    """Update the user's e-mail address."""
    helpers.ensure_users_match(user, uid)
    try:
        email = request.form['email']
    except KeyError:
        raise helpers.BadRequest(errors.MISSING_FIELD,
                                 "missing e-mail address")
    if not mail.is_valid(email):
        raise helpers.BadRequest(errors.INVALID_EMAIL, "e-mail is not valid")
    try:
        user.email = email
        g.store.flush()
    except storm.exceptions.IntegrityError:
        # E-mail already in database.
        raise helpers.BadRequest(errors.EXISTING_USER,
                                 "e-mail already taken by another user")
    return helpers.success()
Example #13
0
def leave_master(user, gid):
    """Leave the DJ spot."""
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    if group.master != None and group.master != user:
        raise helpers.Unauthorized("you are not the master")
    group.master = None
    return helpers.success()
Example #14
0
def get_tracks(master, gid):
    """Get the next tracks."""
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    if group.master != master:
        raise helpers.Unauthorized("you are not the DJ")
    # Get all the tracks in the master's library that haven't been played.
    played_filter = get_played_filter(group)
    remaining = filter(
        played_filter,
        g.store.find(LibEntry,
                     (LibEntry.user == master) & (LibEntry.is_valid == True)
                     & (LibEntry.is_local == True)))
    if len(remaining) == 0:
        raise helpers.NotFound(errors.TRACKS_DEPLETED,
                               'no more tracks to play')
    # Partition tracks based on whether we can embed them in the latent space.
    with_feats = list()
    points = list()
    no_feats = list()
    for entry in remaining:
        point = predict.get_point(entry.track)
        if point is not None:
            with_feats.append(entry)
            points.append(point)
        else:
            no_feats.append(entry)
    # For the users that can be modelled: predict their ratings.
    models = filter(lambda model: model.is_nontrivial(),
                    [predict.Model(user) for user in group.users])
    if models is not None:
        ratings = [model.score(points) for model in models]
        agg = predict.aggregate(ratings)
    else:
        # Not a single user can be modelled! just order the songs randomly.
        agg = range(len(with_feats))
        random.shuffle(agg)
    # Construct the playlist, decreasing order of scores.
    playlist = [
        entry for entry, score in sorted(
            zip(with_feats, agg), key=itemgetter(1), reverse=True)
    ]
    # Randomize songs for which we don't have features.
    random.shuffle(no_feats)
    playlist.extend(no_feats)
    # Craft the JSON response.
    tracks = list()
    for entry in playlist[:MAX_TRACKS]:
        tracks.append({
            'artist': entry.track.artist,
            'title': entry.track.title,
            'local_id': entry.local_id,
        })
    return jsonify(playlist_id=get_playlist_id(group), tracks=tracks)
Example #15
0
def create_group():
    """Create a new group."""
    try:
        name = request.form['name']
        lat = float(request.form['lat'])
        lon = float(request.form['lon'])
    except (KeyError, ValueError):
        raise helpers.BadRequest(
            errors.MISSING_FIELD,
            "group name, latitude or longitude is missing or invalid")
    group = Group(name, is_active=True)
    group.coordinates = geometry.Point(lat, lon)
    g.store.add(group)
    return list_groups()
Example #16
0
def skip_track(user, gid):
    """Skip the track that is currently being played."""
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    if group.master != user:
        raise helpers.Unauthorized("you are not the master")
    results = g.store.find(GroupEvent, (GroupEvent.event_type == events.PLAY)
                           & (GroupEvent.group == group))
    play_event = results.order_by(Desc(GroupEvent.created)).first()
    if play_event is None:
        raise helpers.BadRequest(errors.NO_CURRENT_TRACK, "no track to skip")
    payload = {
        'artist': play_event.payload.get('artist'),
        'title': play_event.payload.get('title'),
        'master': {
            'uid': user.id,
            'nickname': user.nickname
        },
    }
    event = GroupEvent(group, user, events.SKIP, payload)
    g.store.add(event)
    return helpers.success()
Example #17
0
def play_track(user, gid):
    """Register the track that is currently playing."""
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    if group.master != user:
        raise helpers.Unauthorized("you are not the master")
    try:
        artist = request.form['artist']
        title = request.form['title']
    except KeyError:
        raise helpers.BadRequest(errors.MISSING_FIELD,
                                 "missing artist and / or title")
    track = g.store.find(Track, (Track.artist == artist) &
                         (Track.title == title)).one()
    if track is None:
        raise helpers.BadRequest(errors.INVALID_TRACK, "track not found")
    payload = {
        'artist': track.artist,
        'title': track.title,
        'master': {
            'uid': user.id,
            'nickname': user.nickname
        },
    }
    payload['stats'] = list()
    # TODO Something better than random scores :)
    for resident in group.users:
        payload['stats'].append({
            'uid': resident.id,
            'nickname': resident.nickname,
            'score': int(random.random() * 100),
            'predicted': True  #if random.random() > 0.2 else False
        })
    event = GroupEvent(group, user, events.PLAY, payload)
    g.store.add(event)
    return helpers.success()
Example #18
0
def get_group_info(gid):
    """Get infos about the specified group.

    Includes:
    - participants in the group (ID, nickname & stats)
    - current DJ (ID & nickname)
    - info about last track
    """
    group = g.store.get(Group, gid)
    if group is None:
        raise helpers.BadRequest(errors.INVALID_GROUP, "group does not exist")
    userdict = dict()
    for user in group.users:
        userdict[user.id] = {'nickname': user.nickname}
    # Search for the last track that was played.
    results = g.store.find(GroupEvent, (GroupEvent.event_type == events.PLAY)
                           & (GroupEvent.group == group))
    track = None
    play_event = results.order_by(Desc(GroupEvent.created)).first()
    if play_event is not None:
        artist = play_event.payload.get('artist')
        title = play_event.payload.get('title')
        row = g.store.find(Track, (Track.artist == artist)
                           & (Track.title == title)).one()
        image = row.image if row is not None else None
        track = {
            'artist': artist,
            'title': title,
            'image': image,
        }
        for entry in play_event.payload.get('stats', []):
            if entry.get('uid') in userdict:
                uid = entry['uid']
                userdict[uid]['score'] = entry.get('score')
                userdict[uid]['predicted'] = entry.get('predicted', True)
    users = list()
    for key, val in userdict.iteritems():
        users.append({
            'uid': key,
            'nickname': val.get('nickname'),
            'score': val.get('score'),
            'predicted': val.get('predicted', True)
        })
    master = None
    if group.master is not None:
        master = {'uid': group.master.id, 'nickname': group.master.nickname}
    return jsonify(name=group.name, track=track, master=master, users=users)
Example #19
0
def get_user_nickname(uid):
    """Get any user's nickname."""
    user = g.store.get(User, uid)
    if user is None:
        raise helpers.BadRequest(errors.INVALID_USER, "user does not exist")
    return jsonify(uid=user.id, nickname=user.nickname)