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()
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()
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()
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()
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)
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)
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()
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()
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()
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()
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()
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)
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()
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()
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()
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)
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)