def mark_notice_route(request, notice_id): """ Mark notice as read or unread. Must be logged in as user, provide a valid ID, and own the notice. Return notice. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, 'EWoueZr0TYSccUhdNISK3A') notice = get_notice(db_conn, {'id': notice_id}) if not notice: return abort(404, 'xsNPOqJoRw-aUrFo0RhVoA') if notice['user_id'] != current_user['id']: return abort(403, 'xPkb7MYXRIOaI7HeV9U37A') if 'read' not in request['params']: errors = [{ 'name': 'read', 'message': 'You must specify read or unread.', 'ref': 'bvtS4G4jQnaLlVSLyUXjVg', }] elif request['params'].get('read') is True: notice, errors = mark_notice_as_read(db_conn, notice) elif request['params'].get('read') is False: notice, errors = mark_notice_as_unread(db_conn, notice) if errors: return 400, { 'errors': errors, 'ref': 'FeEtTWJJQv22dTpz8y5fZA', } return 200, {'notice': deliver_notice(notice, access='private')}
def remove_set_route(request, user_id, set_id): """ Remove a set from the learner's list of sets. """ current_user = get_current_user(request) if not current_user: return abort(401) if user_id != current_user['id']: return abort(403) uset = UserSets.get(user_id=user_id) if not uset: return 404, { 'errors': [{ 'message': 'User does not have sets.' }], 'ref': '8huZbvEAYOP8LcZb2sXbqNOC' } if set_id not in uset['set_ids']: return abort(404) uset['set_ids'].remove(set_id) usets, errors = uset.save() if errors: return 400, {'errors': errors, 'ref': 'qIfll1e7dbP9V9jmC8FkCwsa'} return 200, {'sets': uset['set_ids']}
def update_topic_route(request, topic_id): """ Update the topic. - Only the name can be changed. - Only by original author. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, 'ZUiN62FFR3OcBM6s8UJSmg') # ## STEP 1) Find existing topic instance ## # topic = get_topic(db_conn, {'id': topic_id}) if not topic: return abort(404, 'MXzNqBU6SN28tNtRXW9rNw') if topic['user_id'] != current_user['id']: return abort(403, 'MZZbJNt3RK-4kVMo2rROWA') # ## STEP 2) Validate and save topic instance ## # topic_data = request['params'] topic, errors = update_topic(db_conn, topic, topic_data) if errors: return 400, { 'errors': errors, 'ref': 'zu7VABcJT5qCzF7BHNCH5w', } # ## STEP 3) Return response ## # return 200, {'topic': deliver_topic(topic)}
def follow_route(request): """ Follow a card, unit, or set. """ # TODO-3 simplify this method. does some of this belong in the model? db_conn = request["db_conn"] current_user = get_current_user(request) if not current_user: return abort(401) follow_data = dict(**request["params"]) follow_data["user_id"] = current_user["id"] follow = Follow(follow_data) errors = follow.validate(db_conn) if errors: return 400, {"errors": errors, "ref": "4Qn9oWVWiGKvXSONQKHSy1T6"} follow, errors = follow.save(db_conn) if errors: return 400, {"errors": errors, "ref": "gKU6wgTItxpKyDs0eAlonCmi"} return 200, {"follow": follow.deliver(access="private")}
def get_follows_route(request): """ Get a list of the users follows. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) params = dict(**request['params']) params['user_id'] = current_user['id'] follows = list_follows(params, db_conn) output = { 'follows': [deliver_follow(follow, access='private') for follow in follows] } # TODO-3 SPLITUP should this be a different endpoint? if 'entities' in request['params']: entities = flush_entities(db_conn, [follow['entity'] for follow in follows]) output['entities'] = [ entity.deliver() if entity else None for entity in entities ] return 200, output
def mark_notice_route(request, notice_id): """ Mark notice as read or unread. Must be logged in as user, provide a valid ID, and own the notice. Return notice. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) notice = get_notice({'id': notice_id}, db_conn) if not notice: return abort(404) if notice['user_id'] != current_user['id']: return abort(403) if 'read' not in request['params']: errors = [{ 'name': 'read', 'message': 'You must specify read or unread.', }] elif request['params'].get('read') is True: notice, errors = mark_notice_as_read(notice, db_conn) elif request['params'].get('read') is False: notice, errors = mark_notice_as_unread(notice, db_conn) if len(errors): return 400, { 'errors': errors, 'ref': 'qR4CBtcfcYfWDTqK9JOXXLhO', } return 200, {'notice': deliver_notice(notice, access='private')}
def next_route(request): """ Tell the learner where to go next. TODO-3 should we move all `next` data from individual endpoints to this one, and have the UI call this endpoint each time to get the next state? """ current_user = get_current_user(request) if not current_user: return abort(401) context = get_learning_context(current_user) # If 'next' action, return that, # else 'next' is GET Choose Subject if context.get('next'): return 200, {'next': context['next']} return 200, { 'next': { 'method': 'GET', 'path': '/s/users/{user_id}/subjects'.format( user_id=convert_uuid_to_slug(current_user['id'])), } }
def next_route(request): """ Tell the learner where to go next. TODO-3 should we move all `next` data from individual endpoints to this one, and have the UI call this endpoint each time to get the next state? """ current_user = get_current_user(request) if not current_user: return abort(401) context = current_user.get_learning_context() # If 'next' action, return that, # else 'next' is GET Choose Set if context.get('next'): return 200, { 'next': context['next'] } return 200, { 'next': { 'method': 'GET', 'path': '/s/users/{user_id}/sets' .format(user_id=current_user['id']), } }
def update_topic_route(request, topic_id): """ Update the topic. - Only the name can be changed. - Only by original author. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) # ## STEP 1) Find existing topic instance ## # topic = get_topic({'id': topic_id}, db_conn) if not topic: return abort(404) if topic['user_id'] != current_user['id']: return abort(403) # ## STEP 2) Limit the scope of changes ## # topic_data = request['params']['topic'] topic_data = pick(topic_data, ('name', )) # ## STEP 3) Validate and save topic instance ## # topic, errors = update_topic(topic, topic_data, db_conn) if errors: errors = prefix_error_names('topic.', errors) return 400, { 'errors': errors, 'ref': 'k7ItNedf0I0vXfiIUcDtvHgQ', } # ## STEP 4) Return response ## # return 200, {'topic': deliver_topic(topic)}
def learn_card_route(request, card_id): """ Render the card's data, ready for learning. NEXT STATE GET Learn Card -> POST Respond Card """ current_user = get_current_user(request) if not current_user: return abort(401) card = get_card_by_kind(card_id) if not card: return abort(404) # Make sure the current unit id matches the card context = current_user.get_learning_context() if context.get('unit', {}).get('entity_id') != card['unit_id']: return abort(400) next_ = { 'method': 'POST', 'path': '/s/cards/{card_id}/responses'.format(card_id=card['entity_id']) } current_user.set_learning_context(card=card.data, next=next_) return 200, { 'card': card.deliver(access=''), 'set': context.get('set'), 'unit': context.get('unit'), 'next': next_, }
def remove_subject_route(request, user_id, subject_id): """ Remove a subject from the learner's list of subjects. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, 'DHlX2XTsTO-1Hhfr9GXkJw') if user_id != current_user['id']: return abort(403, '8yt2d8K1RNKidGIVU1CaOA') user_subjects = list_user_subjects(db_conn, user_id) if not user_subjects: return 404, { 'errors': [{ 'name': 'user_id', 'message': 'User does not have subjects.', 'ref': 'nttevgwMRsOwiT_ul0SmHQ', }], } matches = [ us for us in user_subjects if (convert_slug_to_uuid(us['subject_id']) == convert_slug_to_uuid(subject_id)) ] if not matches: return abort(404, 'AQV0c9qfSdO7Ql2IC8l0bw') errors = remove_user_subject(db_conn, user_id, subject_id) if errors: return 400, { 'errors': errors, 'ref': 'h1BKySSTT0SgH2OTTnSVlA' } return 200, {}
def learn_card_route(request, card_id): """ Render the card's data, ready for learning. NEXT STATE GET Learn Card -> POST Respond Card """ current_user = get_current_user(request) if not current_user: return abort(401) card = get_card_by_kind(card_id) if not card: return abort(404) # Make sure the current unit id matches the card context = current_user.get_learning_context() if context.get('unit', {}).get('entity_id') != card['unit_id']: return abort(400) next_ = { 'method': 'POST', 'path': '/s/cards/{card_id}/responses' .format(card_id=card['entity_id']) } current_user.set_learning_context(card=card.data, next=next_) return 200, { 'card': card.deliver(access=''), 'set': context.get('set'), 'unit': context.get('unit'), 'next': next_, }
def update_topic_route(request, topic_id): """ Update the topic. - Only the name can be changed. - Only by original author. """ current_user = get_current_user(request) if not current_user: return abort(401) # ## STEP 1) Find existing topic instance ## # topic = Topic.get(id=topic_id) if not topic: return abort(404) if topic['user_id'] != current_user['id']: return abort(403) # ## STEP 2) Limit the scope of changes ## # topic_data = request['params']['topic'] topic_data = pick(topic_data, ('name',)) # ## STEP 3) Validate and save topic instance ## # topic, errors = topic.update(topic_data) if errors: errors = prefix_error_names('topic.', errors) return 400, { 'errors': errors, 'ref': 'k7ItNedf0I0vXfiIUcDtvHgQ', } # ## STEP 4) Return response ## # return 200, {'topic': topic.deliver()}
def get_user_sets_route(request, user_id): """ Get the list of sets the user has added. NEXT STATE GET Choose Set -> POST Choose Set """ current_user = get_current_user(request) if not current_user: return abort(401) if user_id != current_user['id']: return abort(403) next_ = { 'method': 'POST', 'path': '/s/users/{user_id}/sets/{set_id}'.format(user_id=current_user['id'], set_id='{set_id}'), } current_user.set_learning_context(next=next_) uset = UserSets.get(user_id=user_id) if not uset: return 200, {'sets': [], 'next': next_} return 200, { 'sets': [s.deliver() for s in uset.list_sets(**request['params'])], 'next': next_, }
def get_user_sets_route(request, user_id): """ Get the list of sets the user has added. NEXT STATE GET Choose Set -> POST Choose Set """ current_user = get_current_user(request) if not current_user: return abort(401) if user_id != current_user['id']: return abort(403) next_ = { 'method': 'POST', 'path': '/s/users/{user_id}/sets/{set_id}' .format(user_id=current_user['id'], set_id='{set_id}'), } current_user.set_learning_context(next=next_) uset = UserSets.get(user_id=user_id) if not uset: return 200, {'sets': [], 'next': next_} return 200, { 'sets': [s.deliver() for s in uset.list_sets(**request['params'])], 'next': next_, }
def get_follows_route(request): """ Get a list of the users follows. """ db_conn = request['db_conn'] current_user = get_current_user(request) user_id = request['params'].get('user_id') if user_id: user = get_user(db_conn, {'id': user_id}) if not user: return abort(404, 'sYkDuhNmReOrKyR0xsBmHg') if (user != current_user and user['settings']['view_follows'] != 'public'): return abort(403, 'FnH15Y3MRma6bU2gXqzjQQ') else: user = current_user if not user: return abort(401, 'YMC5rhI1TOCgUQu6jJeoQg') params = dict(**request['params']) params['user_id'] = user['id'] follows = list_follows_by_user(db_conn, params) return 200, { 'follows': [deliver_follow(follow, access='private') for follow in follows] }
def remove_set_route(request, user_id, set_id): """ Remove a set from the learner's list of sets. """ current_user = get_current_user(request) if not current_user: return abort(401) if user_id != current_user['id']: return abort(403) uset = UserSets.get(user_id=user_id) if not uset: return 404, { 'errors': [{'message': 'User does not have sets.'}], 'ref': '8huZbvEAYOP8LcZb2sXbqNOC' } if set_id not in uset['set_ids']: return abort(404) uset['set_ids'].remove(set_id) usets, errors = uset.save() if errors: return 400, { 'errors': errors, 'ref': 'qIfll1e7dbP9V9jmC8FkCwsa' } return 200, {'sets': uset['set_ids']}
def update_post_route(request, topic_id, post_id): """ Update an existing post. Must be one's own post. For post: - Only the body field may be changed. For proposals: - Only the name, body, and status fields can be changed. - The status can only be changed to declined, and only when the current status is pending or blocked. For votes: - The only fields that can be updated are body and response. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) # ## STEP 1) Find existing post instance ## # post_ = get_post_facade(db_conn, post_id) if not post_: return abort(404) if post_['user_id'] != current_user['id']: return abort(403) post_kind = post_['kind'] # ## STEP 2) Limit the scope of changes ## # post_data = request['params']['post'] if post_kind is 'post': post_data = pick(post_data, ('body',)) elif post_kind is 'proposal': post_data = pick(post_data, ('name', 'body', 'status',)) if (post_data.get('status') != 'declined' or post_data.get('status') not in ('pending', 'blocked',)): del post_data['status'] elif post_kind is 'vote': post_data = pick(post_data, ('body', 'response',)) # ## STEP 3) Validate and save post instance ## # post_, errors = post_.update(db_conn, post_data) if errors: errors = prefix_error_names('post.', errors) return 400, { 'errors': errors, 'ref': 'E4LFwRv2WEJZks7use7TCpww' } # ## STEP 4) Make updates based on proposal / vote status ## # if post_kind == 'proposal': update_entity_status(db_conn, post_) if post_kind == 'vote': proposal = Proposal.get(db_conn, id=post_['replies_to_id']) update_entity_status(db_conn, proposal) # ## STEP 5) Return response ## # return 200, {'post': post_.deliver()}
def remove_subject_route(request, user_id, subject_id): """ Remove a subject from the learner's list of subjects. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) if user_id != current_user['id']: return abort(403) user_subject = get_user_subjects(user_id, db_conn) if not user_subject: return 404, { 'errors': [{ 'message': 'User does not have subjects.' }], 'ref': '8huZbvEAYOP8LcZb2sXbqNOC' } if subject_id not in user_subject['subject_ids']: return abort(404) user_subject, errors = remove_user_subjects(user_id, subject_id, db_conn) if errors: return 400, {'errors': errors, 'ref': 'qIfll1e7dbP9V9jmC8FkCwsa'} return 200, {'subjects': user_subject['subject_ids']}
def get_user_route(request, user_id): """ Get the user by their ID. """ db_conn = request["db_conn"] user = get_user({"id": user_id}, db_conn) current_user = get_current_user(request) # Posts if in request params # Sets if in request params and allowed # Follows if in request params and allowed if not user: return abort(404) data = {} data["user"] = deliver_user(user, access="private" if current_user and user["id"] == current_user["id"] else None) # TODO-2 SPLITUP create new endpoints for these instead if "posts" in request["params"]: data["posts"] = [post.deliver() for post in get_posts_facade(db_conn, user_id=user["id"])] if "sets" in request["params"] and user["settings"]["view_sets"] == "public": u_sets = UserSets.get(db_conn, user_id=user["id"]) data["sets"] = [set_.deliver() for set_ in u_sets.list_sets(db_conn)] if "follows" in request["params"] and user["settings"]["view_follows"] == "public": data["follows"] = [follow.deliver() for follow in Follow.list(db_conn, user_id=user["id"])] if "avatar" in request["params"]: size = int(request["params"]["avatar"]) data["avatar"] = get_avatar(user["email"], size if size else None) return 200, data
def get_current_user_route(request): """ Get current user's information. """ current_user = get_current_user(request) if not current_user: return abort(401, 'l9BCKn1zQ5KRgFRYujqU7g') return 200, {'user': deliver_user(current_user, access='private')}
def get_current_user_route(request): """ Get current user's information. """ current_user = get_current_user(request) if not current_user: return abort(401) return 200, {'user': current_user.deliver(access='private')}
def choose_unit_route(request, subject_id, unit_id): """ Updates the learner's information based on the unit they have chosen. NEXT STATE POST Chosen Unit -> GET Learn Card """ # TODO-3 simplify this method. should it be broken up or moved to model? db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, 'NcrRkqIlSW-BkcWLBkwORQ') unit = get_latest_accepted_unit(db_conn, unit_id) if not unit: return abort(404, 'PIti_qwAQ7WXwQwNZertxw') # If the unit isn't in the subject... context = get_learning_context(current_user) subject_ids = [ convert_uuid_to_slug(subject['entity_id']) for subject in list_subjects_by_unit_recursive(db_conn, unit_id) ] context_subject_id = context.get('subject', {}).get('entity_id') if context_subject_id not in subject_ids: return 400, { 'errors': [{ 'message': 'Unit not in subject.', 'ref': 'r32fH0eCRZCivJoh-hKwZQ', }] } # Or, the unit doesn't need to be learned... status = judge(db_conn, unit, current_user) if status == "done": return 400, { 'errors': [{ 'message': 'Unit not needed.', 'ref': 'YTU27E63Rfiy3Rqmqd6Bew', }] } # Choose a card for the learner to learn card = choose_card(db_conn, current_user, unit) if card: next_ = { 'method': 'GET', 'path': '/s/cards/{card_id}/learn'.format( card_id=convert_uuid_to_slug(card['entity_id'])), } else: next_ = {} set_learning_context(current_user, unit=unit, card=card if card else None, next=next_) return 200, {'next': next_}
def get_current_user_route(request): """ Get current user's information. """ current_user = get_current_user(request) if not current_user: return abort(401) return 200, {"user": deliver_user(current_user, access="private")}
def test_get_current_user(users_table, db_conn): """ Expect to get the current user given session info. """ create_user_in_db(users_table, db_conn) token = log_in() user = get_current_user({'cookies': {'session_id': token}}) assert user assert user['id'] == 'abcd1234'
def choose_unit_route(request, set_id, unit_id): """ Updates the learner's information based on the unit they have chosen. NEXT STATE POST Chosen Unit -> GET Learn Card """ # TODO-3 simplify this method. should it be broken up or moved to model? db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) unit = Unit.get_latest_accepted(db_conn, unit_id) if not unit: return abort(404) # If the unit isn't in the set... context = get_learning_context(current_user) set_ids = [set_['entity_id'] for set_ in Set.list_by_unit_id(db_conn, unit_id)] if context.get('set', {}).get('entity_id') not in set_ids: return abort(400) status = judge(db_conn, unit, current_user) # Or, the unit doesn't need to be learned... if status == "done": return abort(400) # Choose a card for the learner to learn card = choose_card(db_conn, current_user, unit) if card: next_ = { 'method': 'GET', 'path': '/s/cards/{card_id}/learn' .format(card_id=card['entity_id']), } else: next_ = {} set_learning_context( current_user, unit=unit.data, card=card.data if card else None, next=next_ ) return 200, {'next': next_}
def choose_unit_route(request, subject_id, unit_id): """ Updates the learner's information based on the unit they have chosen. NEXT STATE POST Chosen Unit -> GET Learn Card """ # TODO-3 simplify this method. should it be broken up or moved to model? db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) unit = Unit.get_latest_accepted(db_conn, unit_id) if not unit: return abort(404) # If the unit isn't in the subject... context = get_learning_context(current_user) subject_ids = [ subject['entity_id'] for subject in Subject.list_by_unit_id(db_conn, unit_id) ] if context.get('subject', {}).get('entity_id') not in subject_ids: return abort(400) status = judge(db_conn, unit, current_user) # Or, the unit doesn't need to be learned... if status == "done": return abort(400) # Choose a card for the learner to learn card = choose_card(db_conn, current_user, unit) if card: next_ = { 'method': 'GET', 'path': '/s/cards/{card_id}/learn'.format(card_id=card['entity_id']), } else: next_ = {} set_learning_context(current_user, unit=unit.data, card=card.data if card else None, next=next_) return 200, {'next': next_}
def get_my_recently_created_units_route(request): """ Get the units the user most recently created. """ current_user = get_current_user(request) if not current_user: return abort(401, '2fReKMNNQg6BhnmfBI3UiQ') db_conn = request['db_conn'] units = list_my_recently_created_units(db_conn, current_user['id']) return 200, { 'units': [deliver_unit(unit) for unit in units], }
def list_notices_route(request): """ List notices for current user. Take parameters `limit`, `skip`, `tag`, and `read`. """ current_user = get_current_user(request) if not current_user: return abort(401) notices = Notice.list(user_id=current_user['id'], **request['params']) output = {'notices': [notice.deliver(access='private') for notice in notices]} return 200, output
def get_my_recently_created_subjects_route(request): """ Get the subjects the user most recently created. """ current_user = get_current_user(request) if not current_user: return abort(401) db_conn = request['db_conn'] subjects = get_my_recently_created_subjects(current_user, db_conn) return 200, { 'subjects': [subject.deliver() for subject in subjects], }
def get_my_recently_created_subjects_route(request): """ Get the subjects the user most recently created. """ current_user = get_current_user(request) if not current_user: return abort(401, 'MEnwdloNQLWEVnZNT1YRFg') db_conn = request['db_conn'] subjects = list_my_recently_created_subjects(db_conn, current_user['id']) return 200, { 'subjects': [deliver_subject(subject) for subject in subjects], }
def get_my_recently_created_units_route(request): """ Get the units the user most recently created. """ current_user = get_current_user(request) if not current_user: return abort(401) db_conn = request['db_conn'] units = get_my_recently_created_units(current_user, db_conn) return 200, { 'units': [unit.deliver() for unit in units], }
def select_subject_route(request, user_id, subject_id): """ Select the subject to work on. NEXT STATE POST Choose Subject (Update Learner Context) -> GET Choose Subject ...when subject is complete -> GET Choose Unit ...when in learn or review mode -> GET Learn Card ...when in diagnosis (Unit auto chosen) """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, 'f8IynoM9RLmW0Ae14_Hukw') subject = get_latest_accepted_subject(db_conn, subject_id) set_learning_context(current_user, subject=subject) buckets = traverse(db_conn, current_user, subject) # When in diagnosis, choose the unit and card automagically. # if buckets.get('diagnose'): # unit = buckets['diagnose'][0] # card = choose_card(db_conn, current_user, unit) # next_ = { # 'method': 'GET', # 'path': '/s/cards/{card_id}/learn' # .format(card_id=convert_uuid_to_slug(card['entity_id'])), # } # set_learning_context( # current_user, # next=next_, unit=unit, card=card) # When in learn or review mode, lead me to choose a unit. # elif buckets.get('review') or if buckets.get('learn'): next_ = { 'method': 'GET', 'path': '/s/subjects/{subject_id}/units'.format( subject_id=convert_uuid_to_slug(subject_id) ), } set_learning_context(current_user, next=next_) # If the subject is complete, lead the learner to choose another subject. else: next_ = { 'method': 'GET', 'path': '/s/users/{user_id}/subjects'.format( user_id=convert_uuid_to_slug(current_user['id']) ), } set_learning_context(current_user, next=next_, unit=None, subject=None) return 200, {'next': next_}
def list_notices_route(request): """ List notices for current user. Take parameters `limit`, `skip`, `tag`, and `read`. """ current_user = get_current_user(request) if not current_user: return abort(401) notices = Notice.list(user_id=current_user['id'], **request['params']) output = { 'notices': [notice.deliver(access='private') for notice in notices] } return 200, output
def create_topic_route(request): """ Create a new topic. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, 'WJ50hh2STw-5ujy62wyXew') # ## STEP 1) Create topic topic_data = request['params'] if not topic_data: return 400, { 'errors': [{ 'name': 'topic', 'message': 'Missing topic data.', 'ref': 'PmocSz4OQUGa2T7x98yVlg', }], } topic_data['user_id'] = current_user['id'] topic, errors = insert_topic(db_conn, topic_data) if errors: return 400, {'errors': errors, 'ref': 'UoyXf_vwSWee0tCWgxg4Zw'} # ## STEP 2) Add author as a follower insert_follow( db_conn, { 'user_id': current_user['id'], 'entity_id': topic['id'], 'entity_kind': 'topic', }) # TODO-2 also follow the entity automatically IF needed # ## STEP 3) Send out any needed notices send_notices(db_conn, entity_id=topic['entity_id'], entity_kind=topic['entity_kind'], notice_kind='create_topic', notice_data={ 'user_name': current_user['name'], 'topic_name': topic['name'], 'entity_kind': topic['entity_kind'], 'entity_name': convert_uuid_to_slug(topic['entity_id']), }) # ## STEP 4) Return response return 200, { 'topic': deliver_topic(topic), }
def create_topic_route(request): """ """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) user_id = current_user['id'] if user_id != "NNKkHsjE3pEOW0wsPaQJm9MD": return abort(403) data = request['params'] if not data: return abort(404) for key, unit in data.get('units').items(): unit_entity_id, unit_version_id = inject_unit(unit, db_conn) topic_id = inject_topic('unit', unit, unit_entity_id, user_id, db_conn) proposal_id = inject_proposal('unit', unit, user_id, topic_id, unit_version_id, db_conn) inject_votes(topic_id, proposal_id, db_conn) update_status('unit', unit_version_id, db_conn) for card in unit.get('video', []): kind = 'video' entity_id, version_id = inject_video_card(unit_entity_id, card, db_conn) topic_id = inject_topic(kind, card, entity_id, user_id, db_conn) proposal_id = inject_proposal(kind, card, user_id, topic_id, version_id, db_conn) inject_votes(topic_id, proposal_id, db_conn) update_status('card', version_id, db_conn) for card in unit.get('choice', []): kind = 'choice' entity_id, version_id = inject_choice_card(unit_entity_id, card, db_conn) topic_id = inject_topic(kind, card, entity_id, user_id, db_conn) proposal_id = inject_proposal(kind, card, user_id, topic_id, version_id, db_conn) inject_votes(topic_id, proposal_id, db_conn) update_status('card', version_id, db_conn) return 200, 'OK'
def list_notices_route(request): """ List notices for current user. Take parameters `limit`, `skip`, `tag`, and `read`. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, '9oMIw3V8S3WeaLf9IgbmaQ') params = extend({}, request['params'], {'user_id': current_user['id']}) notices = list_notices(db_conn, params) output = {'notices': [deliver_notice(notice, access='private') for notice in notices]} return 200, output
def test_get_current_user(db_conn): """ Expect to get the current user given session info. """ create_user_in_db(db_conn) token = log_in() user = get_current_user({ 'cookies': { 'session_id': token }, 'db_conn': db_conn, }) assert user assert user['id'] == convert_slug_to_uuid(user_id)
def list_notices_route(request): """ List notices for current user. Take parameters `limit`, `skip`, `tag`, and `read`. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) params = extend({}, request['params'], {'user_id': current_user['id']}) notices = list_notices(params, db_conn) output = {'notices': [deliver_notice(notice, access='private') for notice in notices]} return 200, output
def follow_route(request): """ Follow a card, unit, or subject. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401, '0kW_gcpzQ7GomlCM28R8hw') follow_data = dict(**request['params']) follow_data['user_id'] = current_user['id'] follow, errors = insert_follow(db_conn, follow_data) if errors: return 400, {'errors': errors, 'ref': 'R4AAxO7PT7udr2huRHIbnA'} return 200, {'follow': deliver_follow(follow, access='private')}
def update_user_route(request, user_id): """ Update the user. Must be the current user. """ db_conn = request["db_conn"] user = get_user({"id": user_id}, db_conn) current_user = get_current_user(request) if not user: return abort(404) if not user["id"] == current_user["id"]: return abort(401) user, errors = update_user(user, request["params"], db_conn) if len(errors): return 400, {"errors": errors, "ref": "AS7LCAWiOOyeEbNOrbsegVY9"} return 200, {"user": deliver_user(user, access="private")}
def get_user_route(request, user_id): """ Get the user by their ID. """ db_conn = request['db_conn'] user = get_user(db_conn, {'id': user_id}) if not user: return abort(404, 'Tp5JnWO1SWms2lTdhw3bJQ') current_user = get_current_user(request) access = 'private' if (current_user and user['id'] == current_user['id']) else None data = {'user': deliver_user(user, access)} if 'avatar' in request['params']: size = int(request['params']['avatar']) or None data['avatar'] = get_avatar(user['email'], size) return 200, data
def follow_route(request): """ Follow a card, unit, or set. """ # TODO-3 simplify this method. does some of this belong in the model? current_user = get_current_user(request) if not current_user: return abort(401) follow_data = dict(**request['params']) follow_data['user_id'] = current_user['id'] follow = Follow(follow_data) errors = follow.validate() if errors: return 400, { 'errors': errors, 'ref': '4Qn9oWVWiGKvXSONQKHSy1T6' } # Ensure the entity exists TODO-3 should this be a model validation? if follow['entity']['kind'] == 'topic': entity = Topic.get(id=follow['entity']['id']) else: entity = get_latest_accepted(follow['entity']['kind'], follow['entity']['id']) if not entity: return abort(404) # Ensure we don't already follow TODO-3 should this be a model validation? prev = Follow.list(user_id=current_user['id'], entity_id=follow_data['entity']['id']) if prev: return abort(409) follow, errors = follow.save() if errors: return 400, { 'errors': errors, 'ref': 'gKU6wgTItxpKyDs0eAlonCmi', } return 200, {'follow': follow.deliver(access='private')}
def follow_route(request): """ Follow a card, unit, or set. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) follow_data = dict(**request['params']) follow_data['user_id'] = current_user['id'] follow, errors = insert_follow(follow_data, db_conn) if errors: return 400, {'errors': errors, 'ref': '4Qn9oWVWiGKvXSONQKHSy1T6'} return 200, {'follow': deliver_follow(follow, access='private')}
def update_user_route(request, user_id): """ Update the user. Must be the current user. """ user = User.get(id=user_id) current_user = get_current_user(request) if not user: return abort(404) if not user['id'] == current_user['id']: return abort(401) user, errors = user.update(request['params']) if len(errors): return 400, { 'errors': errors, 'ref': 'AS7LCAWiOOyeEbNOrbsegVY9', } return 200, {'user': user.deliver(access='private')}
def add_set_route(request, user_id, set_id): """ Add a set to the learner's list of sets. """ db_conn = request['db_conn'] current_user = get_current_user(request) if not current_user: return abort(401) if user_id != current_user['id']: return abort(403) set_ = Set.get(db_conn, entity_id=set_id) if not set_: return abort(404) uset = UserSets.get(db_conn, user_id=user_id) if uset and set_id in uset['set_ids']: return 400, { 'errors': [{ 'name': 'set_id', 'message': 'Set is already added.', }], 'ref': 'kPZ95zM3oxFDGGl8vBdR3J3o', } if uset: uset['set_ids'].append(set_id) uset, errors = uset.save(db_conn) else: uset, errors = UserSets.insert(db_conn, { 'user_id': user_id, 'set_ids': [set_id], }) if errors: return 400, { 'errors': errors, 'ref': 'zCFUbLBTg9n2DnTkQYbqO4X9' } return 200, {'sets': uset['set_ids']}
def get_follows_route(request): """ Get a list of the users follows. """ db_conn = request["db_conn"] current_user = get_current_user(request) if not current_user: return abort(401) follows = Follow.list(db_conn, user_id=current_user["id"], **request["params"]) output = {"follows": [follow.deliver(access="private") for follow in follows]} # TODO-3 SPLITUP should this be a different endpoint? if "entities" in request["params"]: entities = flush_entities(db_conn, [follow["entity"] for follow in follows]) output["entities"] = [entity.deliver() if entity else None for entity in entities] return 200, output
def get_set_units_route(request, set_id): """ Present a small number of units the learner can choose from. NEXT STATE GET Choose Unit -> POST Choose Unit """ db_conn = request['db_conn'] # TODO-3 simplify this method. should it be part of the models? current_user = get_current_user(request) if not current_user: return abort(401) context = get_learning_context(current_user) next_ = { 'method': 'POST', 'path': '/s/sets/{set_id}/units/{unit_id}' .format(set_id=context.get('set', {}).get('entity_id'), unit_id='{unit_id}'), } set_learning_context(current_user, next=next_) set_ = Set.get_latest_accepted(db_conn, set_id) # Pull a list of up to 5 units to choose from based on priority. buckets = traverse(db_conn, current_user, set_) units = buckets['learn'][:5] # TODO-3 Time estimates per unit for mastery. return 200, { 'next': next_, 'units': [unit.deliver() for unit in units], # For the menu, it must return the name and ID of the set 'set': set_.deliver(), 'current_unit_id': context.get('unit', {}).get('entity_id'), }
def select_set_route(request, user_id, set_id): """ Select the set to work on. NEXT STATE POST Choose Set (Update Learner Context) -> GET View Set Tree """ current_user = get_current_user(request) if not current_user: return abort(401) set_ = Set.get_latest_accepted(set_id) next_ = { 'method': 'GET', 'path': '/s/sets/{set_id}/tree' .format(set_id=set_id), } current_user.set_learning_context(set=set_.data, next=next_) return 200, {'next': next_}
def get_follows_route(request): """ Get a list of the users follows. """ current_user = get_current_user(request) if not current_user: return abort(401) follows = Follow.list(user_id=current_user['id'], **request['params']) output = { 'follows': [follow.deliver(access='private') for follow in follows] } # TODO-3 SPLITUP should this be a different endpoint? if 'entities' in request['params']: entities = flush_entities(follow['entity'] for follow in follows) output['entities'] = [entity.deliver() if entity else None for entity in entities] return 200, output
def unfollow_route(request, follow_id): """ Remove a follow. Must be current user's own follow. """ db_conn = request["db_conn"] current_user = get_current_user(request) if not current_user: return abort(401) follow = Follow.get(db_conn, id=follow_id) if not follow: return abort(404) if follow["user_id"] != current_user["id"]: return abort(403) follow, errors = follow.delete(db_conn) if errors: return 400, {"errors": errors, "ref": "iGmpx8UwoFcKNmSKq9Aocy1a"} return 200, {}
def get_user_route(request, user_id): """ Get the user by their ID. """ user = User.get(id=user_id) current_user = get_current_user(request) # Posts if in request params # Sets if in request params and allowed # Follows if in request params and allowed if not user: return abort(404) data = {} data['user'] = user.deliver(access='private' if current_user and user['id'] == current_user['id'] else None) # TODO-2 SPLITUP create new endpoints for these instead if 'posts' in request['params']: data['posts'] = [post.deliver() for post in get_posts_facade(user_id=user['id'])] if ('sets' in request['params'] and user['settings']['view_sets'] == 'public'): u_sets = UserSets.get(user_id=user['id']) data['sets'] = [set_.deliver() for set_ in u_sets.list_sets()] if ('follows' in request['params'] and user['settings']['view_follows'] == 'public'): data['follows'] = [follow.deliver() for follow in Follow.list(user_id=user['id'])] if 'avatar' in request['params']: size = int(request['params']['avatar']) data['avatar'] = user.get_avatar(size if size else None) return 200, data
def unfollow_route(request, follow_id): """ Remove a follow. Must be current user's own follow. """ current_user = get_current_user(request) if not current_user: return abort(401) follow = Follow.get(id=follow_id) if not follow: return abort(404) if follow['user_id'] != current_user['id']: return abort(403) follow, errors = follow.delete() if errors: return 400, { 'errors': errors, 'ref': 'iGmpx8UwoFcKNmSKq9Aocy1a' } return 200, {}
def get_set_tree_route(request, set_id): """ Render the tree of units that exists within a set. Contexts: - Search set, preview units in set - Pre diagnosis - Learner view progress in set - Set complete NEXT STATE GET View Set Tree -> GET Choose Set ...when set is complete -> GET Choose Unit ...when in learn or review mode -> GET Learn Card ...when in diagnosis (Unit auto chosen) TODO-2 merge with get_set_units_route TODO-2 simplify this method """ db_conn = request['db_conn'] set_ = Set.get(db_conn, entity_id=set_id) if not set_: return abort(404) units = set_.list_units(db_conn) # For the menu, it must return the name and ID of the set output = { 'set': set_.deliver(), 'units': [u.deliver() for u in units], } current_user = get_current_user(request) if not current_user: return 200, output context = get_learning_context(current_user) if current_user else {} buckets = traverse(db_conn, current_user, set_) output['buckets'] = { 'diagnose': [u['entity_id'] for u in buckets['diagnose']], 'review': [u['entity_id'] for u in buckets['review']], 'learn': [u['entity_id'] for u in buckets['learn']], 'done': [u['entity_id'] for u in buckets['done']], } # If we are just previewing, don't update anything if set_id != context.get('set', {}).get('entity_id'): return 200, output # When in diagnosis, choose the unit and card automagically. if buckets['diagnose']: unit = buckets['diagnose'][0] card = choose_card(db_conn, current_user, unit) next_ = { 'method': 'GET', 'path': '/s/cards/{card_id}/learn' .format(card_id=card['entity_id']), } set_learning_context( current_user, next=next_, unit=unit.data, card=card.data) # When in learn or review mode, lead me to choose a unit. elif buckets['review'] or buckets['learn']: next_ = { 'method': 'GET', 'path': '/s/sets/{set_id}/units' .format(set_id=set_id), } set_learning_context(current_user, next=next_) # If the set is complete, lead the learner to choose another set. else: next_ = { 'method': 'GET', 'path': '/s/users/{user_id}/sets' .format(user_id=current_user['id']), } set_learning_context(current_user, next=next_, unit=None, set=None) output['next'] = next_ return 200, output
def create_post_route(request, topic_id): """ Create a new post on a given topic. Proposal: must include entity (card, unit, or set) information. Vote: must refer to a valid proposal. """ current_user = get_current_user(request) if not current_user: return abort(401) topic = Topic.get(id=topic_id) if not topic: return 404, { 'errors': [{ 'name': 'topic_id', 'message': c('no_topic'), }], 'ref': 'PCSFCxsJtnlP0x9WzbPoKcwM', } # ## STEP 1) Create post (and entity) instances post_data = request['params'].get('post') if not post_data: return 400, { 'errors': [{ 'name': 'post', 'message': 'Missing post data.', }], 'ref': 'ykQpZwJKq54MTCxgkx0p6baW' } post_data = omit(post_data, ('id', 'created', 'modified',)) post_data['user_id'] = current_user['id'] post_data['topic_id'] = topic_id post_ = instance_post_facade(post_data) post_kind = post_['kind'] if post_kind == 'proposal': entity = instance_new_entity(request['params']) entity_kind = get_kind(request['params']) post_['entity_version'] = { 'id': entity['id'], 'kind': entity_kind, } # ## STEP 2) Validate post (and entity) instances errors = prefix_error_names('post.', post_.validate()) if post_kind == 'proposal': errors = errors + prefix_error_names('entity.', entity.validate()) if len(errors): return 400, { 'errors': errors, 'ref': 'tux33ztgFj9ittSpS7WKIkq7' } # ## STEP 3) Save post (and entity) post_.save() if post_kind == 'proposal': entity.save() # ## STEP 4) Add author as a follower Follow.insert({ 'user_id': current_user['id'], 'entity': { 'id': topic['id'], 'kind': 'topic', } }) # TODO-2 also follow the entity # ## STEP 5) Make updates based on proposal / vote status if post_kind == 'proposal': update_entity_status(post_) if post_kind == 'vote': proposal = Proposal.get(id=post_['replies_to_id']) update_entity_status(proposal) # ## STEP 6) Return response return 200, {'post': post_.deliver()}
def respond_to_card_route(request, card_id): """ Record and process a learner's response to a card. NEXT STATE POST Respond Card -> GET Learn Card ...when not ready -> GET Choose Unit ...when ready, but still units -> GET View Set Tree ...when ready and done """ # TODO-3 simplify this method. # perhaps smaller methods or move to model layer? current_user = get_current_user(request) if not current_user: return abort(401) card = get_card_by_kind(card_id) if not card: return abort(404) # Make sure the card is the current one context = current_user.get_learning_context() if context.get('card', {}).get('entity_id') != card['entity_id']: return abort(400) r = seq_update(current_user, card, request['params'].get('response')) errors, response, feedback = (r.get('errors'), r.get('response'), r.get('feedback')) if errors: return 400, { 'errors': errors, 'ref': 'wtyOJPoy4bh76OIbYp8mS3LP', } set_ = Set(context.get('set')) unit = Unit(context.get('unit')) status = judge(unit, current_user) # If we are done with this current unit... if status == "done": buckets = traverse(current_user, set_) # If there are units to be diagnosed... if buckets['diagnose']: unit = buckets['diagnose'][0] next_card = choose_card(current_user, unit) next_ = { 'method': 'GET', 'path': '/s/cards/{card_id}/learn' .format(card_id=next_card['entity_id']), } current_user.set_learning_context( card=next_card.data, unit=unit.data, next=next_) # If there are units to be learned or reviewed... elif buckets['learn'] or buckets['review']: next_ = { 'method': 'GET', 'path': '/s/sets/{set_id}/units' .format(set_id=set_['entity_id']), } current_user.set_learning_context(card=None, unit=None, next=next_) # If we are out of units... else: next_ = { 'method': 'GET', 'path': '/s/sets/{set_id}/tree' .format(set_id=set_['entity_id']), } current_user.set_learning_context(card=None, unit=None, next=next_) # If we are still reviewing, learning or diagnosing this unit... else: next_card = choose_card(current_user, unit) if next_card: next_ = { 'method': 'GET', 'path': '/s/cards/{card_id}/learn' .format(card_id=next_card['entity_id']), } current_user.set_learning_context(card=next_card.data, next=next_) else: next_ = {} current_user.set_learning_context(next=next_) return 200, { 'response': response.deliver(), 'feedback': feedback, 'next': next_, }
def create_topic_route(request): """ Create a new topic. The first post (or proposal) must be provided. """ current_user = get_current_user(request) if not current_user: return abort(401) # ## STEP 1) Create post and topic (and entity) instances topic_data = request['params'].get('topic') post_data = request['params'].get('post') if not topic_data: return 400, { 'errors': [{ 'name': 'topic', 'message': 'Missing topic data.' }], 'ref': 'zknSd46f2hRNjSjVHCg6YLwN' } if not post_data: return 400, { 'errors': [{ 'name': 'post', 'message': 'Missing post data.' }], 'ref': 'Qki4oWX4nTdNAjYI8z5iNawr' } topic_data = omit(topic_data, ('id', 'created', 'modified')) topic_data['user_id'] = current_user['id'] topic = Topic(topic_data) post_data = omit(post_data, ('id', 'created', 'modified',)) post_data['user_id'] = current_user['id'] post_data['topic_id'] = topic['id'] post_ = instance_post_facade(post_data) post_kind = post_['kind'] if post_kind == 'proposal': entity = instance_new_entity(request['params']) entity_kind = get_kind(request['params']) post_['entity_version'] = { 'id': entity['id'], 'kind': entity_kind, } # ## STEP 2) Validate post and topic (and entity) instances errors = prefix_error_names('topic.', topic.validate()) errors = errors + prefix_error_names('post.', post_.validate()) if post_kind == 'proposal': errors = errors + prefix_error_names('entity.', entity.validate()) if len(errors): return 400, { 'errors': errors, 'ref': 'TAY5pX3ghWBkSIVGTHzpQySa' } # ## STEP 3) Save post and topic (and entity) topic.save() post_.save() if post_kind == 'proposal': entity.save() # ## STEP 4) Add author as a follower Follow.insert({ 'user_id': current_user['id'], 'entity': { 'id': topic['id'], 'kind': 'topic', } }) # TODO-2 also follow the entity automatically IF needed # ## STEP 5) Send out any needed notifications send_notices( entity_id=topic['entity']['id'], entity_kind=topic['entity']['kind'], notice_kind='create_topic', notice_data={ 'user_name': current_user['name'], 'topic_name': topic['name'], 'entity_kind': topic['entity']['kind'], 'entity_name': topic['entity']['id'], } ) # ## STEP 5) Return response return 200, {'topic': topic.deliver(), 'post': post_.deliver()}