def save_story(): if current_user is not None and current_user.admin == True: story_id = request.form['story_id'] story_data = json.loads(request.form['story_data']) confirm_save = request.form['confirm_save'] != 'false' live_story_id = db.collection('application_states').document('application_state').get().get('active_story_id') print(story_id) stories = db.collection('stories') for doc in list(stories.list_documents()): doc = doc.id loop_name = stories.document(doc).get().get('story_name') loop_id = stories.document(doc).get().get('story_id') if loop_name == story_data['story_name'] and loop_id != story_id: # Attempting to save something with a duplicate name msg = 'Attempted to save engine with name already used by engine with ID ' + loop_id + '. To save, change current story name or overwrite existing engine with ID' return {'success': False, 'retry': False, 'msg': msg}, 200, {'ContentType': 'application/json'} if not confirm_save: if story_id == live_story_id: return {'success': False, 'retry': False, 'msg': 'Attempted to overwrite live enine. Duplicate engine and try saving again.'}, 200, {'ContentType': 'application/json'} # return {'success': False, 'rename': False}, 200, {'ContentType': 'application/json'} elif stories.document(story_id).get().exists: return {'success': False, 'retry': True, 'msg': 'You are about to overwrite an existing engine. Are you sure you want to overwrite? If not, try saving with a different engine ID'}, 200, {'ContentType': 'application/json'} # return {'success': False, 'rename': True}, 200, {'ContentType': 'application/json'} if stories.document(story_id).get().exists: stories.document(story_id).update(story_data) else: stories.document(story_id).set(story_data) return {'success': True, 'retry': False, 'msg': 'Story successfully saved'}, 200, {'ContentType': 'application/json'}
def save(self): """save() Saves the UserActivity object to the Firestore database """ # Gets the document containing the user activity activity_doc = db.collection(UserActivity.__collection_name).where( 'user_id', '==', self.user_id).get() # If the user activity already has a document, updates the values if activity_doc: activity_ref = db.collection( UserActivity.__collection_name).document(activity_doc[0].id) activity_ref.update({ 'user_id': self.user_id, 'story_activity': self.story_activity }) # If the user activity does not already have a document, creates a new document else: db.collection(UserActivity.__collection_name).add({ 'user_id': self.user_id, 'story_activity': self.story_activity })
def save(self): """save() Saves the Session object to the Firestore database """ # Gets the document containing the session session_doc = db.collection(Session.__collection_name).where( 'session_key', '==', self.session_key).get() # If the user already has a document, updates the values if session_doc: session_ref = db.collection(Session.__collection_name).document( session_doc[0].id) session_ref.update({ 'session_key': self.session_key, 'user_id': self.user_id }) # If the user does not already have a document, creates a new document else: db.collection(Session.__collection_name).add({ 'session_key': self.session_key, 'user_id': self.user_id })
def update_live_story(): if current_user is not None and current_user.admin == True: new_live_story = request.form['new_live_story'] live_story_data = {'active_story_id': new_live_story, 'active_story_ref': db.collection('stories').document(new_live_story)} app_states = db.collection('application_states') app_states.document('application_state').update(live_story_data) return {'list': new_live_story}, 200
def save(self): """save() Saves the User object to the Firestore database """ # Gets the document containing the user user_doc = db.collection(User.__collection_name).where( 'email', '==', self.email).get() # If the user already has a document, updates the values if user_doc: user_ref = db.collection(User.__collection_name).document( user_doc[0].id) user_ref.update({ 'email': self.email, 'password': self.password, 'salt': self.salt, 'first_name': self.first_name, 'last_name': self.last_name, 'authenticated': self.authenticated, 'admin': self.admin, 'last_activity': self.last_activity, 'favorites': self.favorites, 'history': self.history, 'temp_password': self.temp_password, 'temp_password_expire': self.temp_password_expire }) # If the user does not already have a document, creates a new document else: db.collection(User.__collection_name).add({ 'email': self.email, 'password': self.password, 'salt': self.salt, 'first_name': self.first_name, 'last_name': self.last_name, 'authenticated': self.authenticated, 'admin': self.admin, 'last_activity': self.last_activity, 'favorites': self.favorites, 'history': self.history, 'temp_password': self.temp_password, 'temp_password_expire': self.temp_password_expire })
def delete_engine(): if current_user is not None and current_user.admin == True: live_story = db.collection('application_states').document('application_state').get().get('active_story_id') engine_id = request.form['engine_id'] if engine_id == live_story: return {'success': False}, 200 print(engine_id) print(db.collection('stories').document(engine_id).get().get('story_name')) db.collection('stories').document(engine_id).delete() return {'success': True}, 200
def view_live_story(): if current_user is not None and current_user.admin == True: app_states = db.collection('application_states') # all_stories = [story for story in stories] app_state = list(app_states.list_documents())[0] live_story = 'NONE' try: live_story = app_state.get().get('active_story_id') story_name = db.collection('stories').document(live_story).get().get('story_name') except KeyError: print('No Live Story Field') return {'story_id': live_story, 'story_name': story_name}, 200 return None, 200
def get_all_stories(): if current_user is not None and current_user.admin == True: stories = db.collection('stories') # all_stories = [story for story in stories] child_doc = list(stories.list_documents()) child_doc = [child.id for child in child_doc] story_names = [] for doc in child_doc: story_names.append(db.collection('stories').document(doc).get().get('story_name')) return {'story_id': child_doc, 'story_name': story_names}, 200 return None, 500
def favorites(): """favorites() Serves the favorites page with the user's favorites Accessed at '/favorites' via a GET request Requires that the user is logged in """ # Creates an array of the user's favorites with information about each page favorites = [] for favorite in current_user.favorites: # Gets the story the favorite belongs to story_ref = db.collection('stories').document(favorite['story']) story_doc = story_ref.get() # Gets the page data for the favorite page = story_doc.get('page_nodes.`' + favorite['page_id'] + '`') # Adds the favorite's page name, the favorite's page link, and which history the favorite belongs to favorites.insert(0, (page['page_name'], favorite['story'] + "/" + favorite['page_id'], favorite['history_id'])) # Returns the favorites.html template with the given values return render_response( render_template('user_pages/favorites.html', first_name=current_user.first_name, favorites=favorites))
def get_session(session_key=None, user_id=None): """get_session(session_key=None, user_id=None) Retrieves a session based on the query parameters and returns a Session object with the values populated from Firestore """ # Creates a query on the user collection query = db.collection(Session.__collection_name) # Filters the query by session key, if one is provided if session_key: query = query.where('session_key', '==', session_key) # Filters the query by user ID, if one is provided if user_id: query = query.where('user_id', '==', user_id) # Checks that exactly one session is found. If no sessions are found, we want to return None. If multiple sessions are found, # we want to return None to ensure that no one can access a session they are not meant to. query = query.get() if len(query) != 1: return None # Creates and returns a Session object with the values from Firestore return Session(user_id=query[0].get('user_id'), session_key=query[0].get('session_key'))
def get_user(email=None): """get_user(email=None) Retrieves a user based on the query parameters and returns a User object with the values populated from Firestore """ # Creates a query on the user collection query = db.collection(User.__collection_name) # Filters the query by email, if one is provided if email: query = query.where('email', '==', email) # Checks that exactly one user is found. If no users are found, we want to return None. If multiple users are found, # we want to return None to ensure that no one can access a user account they are not meant to. query = query.get() if len(query) != 1: return None # Creates and returns a User object with the values from Firestore return User(email=query[0].get('email'), password=query[0].get('password'), salt=query[0].get('salt'), first_name=query[0].get('first_name'), last_name=query[0].get('last_name'), authenticated=query[0].get('authenticated'), admin=query[0].get('admin'), last_activity=query[0].get('last_activity'), favorites=query[0].get('favorites'), history=query[0].get('history'), temp_password=query[0].get('temp_password'), temp_password_expire=query[0].get('temp_password_expire'))
def get_user_activity(user_id): """get_user_activity(user_id=None) Retrieves a session based on the query parameters and returns a Session object with the values populated from Firestore """ # Creates a query on the user collection query = db.collection(UserActivity.__collection_name) # Filters the query by user ID, if one is provided if user_id: query = query.where('user_id', '==', user_id) # Checks that exactly one user activity is found. If no user activities are found, we want to create a new user activity. # If multiple user activities are found, we want to return None to ensure that no one can access a user activity they are # not meant to. query = query.get() if len(query) != 1: # If no user activities are found, creates a new one if len(query) == 0: user = UserActivity(user_id=user_id) user.save() return user return None # Creates and returns a UserActivity object with the values from Firestore return UserActivity(user_id=query[0].get('user_id'), story_activity=query[0].get('story_activity'))
def open_story(story_id): if current_user is not None and current_user.admin == True: story_data = db.collection('stories').document(story_id) if story_data.get().exists: # return story_data # should return some success code # return json.dumps({'success': True}), 200, story_data.get().to_dict() return story_data.get().to_dict(), 200 return None, 500 # should be an error code of some sort
def load_all_stories(): if current_user is not None and current_user.admin == True: stories = db.collection('stories') # all_stories = [story for story in stories] child_doc = list(stories.list_documents()) child_doc = [child.id for child in child_doc] return {'list': child_doc}, 200 return None, 500
def __init__(self, user_id, session_key=None): """Session(user_id, session_key=None) Creates a new Session object """ self.user_id = user_id # Checks if a session key was provided, if not it generates a new one and stores the session in Firestore if session_key: self.session_key = session_key else: # Generates a session key self.session_key = str(uuid.uuid4()) # Saves the session to Firestore db.collection(Session.__collection_name).add({ 'session_key': self.session_key, 'user_id': self.user_id })
def index(): """index() Serves the home page Accessed at '/' via a GET request """ # Checks if the user is logged in if current_user: # Checks if the user is an admin if current_user.admin: # Returns the admin homepage return render_response( render_template('admin_pages/homepage.html', first_name=current_user.first_name)) # Gets the active engine to link to with the 'Begin Story' button begin_story = db.collection('application_states').document( 'application_state').get().get('active_story_id') # Gets the most recent story to link to with the 'Continue Story' button most_recent_history = None continue_story = None # Loops over the stories in the user's history for index, history in enumerate(current_user.history): # Sets the continue_story to the first story in the user's history if most_recent_history is None: most_recent_history = index continue_story = current_user.history[most_recent_history][ 'story'] + '/' + current_user.history[most_recent_history][ 'pages'][-1] # Checks if each story was more recently accessed than the current_story; if so, updates the current_story elif history['last_updated'].replace( tzinfo=None) > current_user.history[most_recent_history][ 'last_updated'].replace(tzinfo=None): most_recent_history = index continue_story = current_user.history[most_recent_history][ 'story'] + '/' + current_user.history[most_recent_history][ 'pages'][-1] # Returns the user homepage return render_response( render_template('user_pages/homepage.html', first_name=current_user.first_name, begin_story=begin_story, continue_story=continue_story, history=most_recent_history)) # Returns the homepage return render_response(render_template('home.html'))
def delete_session(session_key=None, user_id=None): """delete_session(session_key=None, user_id=None) Deletes a session based on the query parameters and returns a Session object with the values populated from Firestore """ # Creates a query on the user collection query = db.collection(Session.__collection_name) # Filters the query by session key, if one is provided if session_key: query = query.where('session_key', '==', session_key) # Filters the query by user ID, if one is provided if user_id: query = query.where('user_id', '==', user_id) # Checks that exactly one session is found. If no sessions are found, we want to return None. If multiple sessions are found, # we want to do nothing so that we don't end a session we don't intend to query = query.get() if len(query) == 1: # Deletes the session from Firestore db.collection(Session.__collection_name).document( query[0].id).delete()
def history(): """history() Serves the history page with the user's history Accessed at '/history' via a GET request Requires that the user is logged in """ # The current user's history history = current_user.history # The array with the history information to pass to the history page # [[(page_id, history)]] history_arr = [] # Sorts the history for i in range(len(history)): for j in range(i + 1, len(history)): if history[i]['last_updated'].replace( tzinfo=None) < history[j]['last_updated'].replace( tzinfo=None): history[i], history[j] = history[j], history[i] # Builds the array to pass to the history page for hist in history: # The array with the history information for this history new_arr = [] # Gets the story the history belongs to story_ref = db.collection('stories').document(hist['story']) story_doc = story_ref.get() # Adds each page in this history to the array for page_id in hist['pages']: # Gets the page data by page ID page = story_doc.get('page_nodes.`' + page_id + '`') # Adds the page's name and link new_arr.insert(0, (page['page_name'], hist['story'] + "/" + page_id)) # Appends the history_arr.append(new_arr) # Returns the history page with the history data return render_response( render_template('user_pages/history.html', history=history_arr))
def get_all_users(): """get_all_users() Retrieves basic information for all users in Firestore Only returns their email, first name, last name, last activity, and admin status Does not return sensitive data like password and salt """ users = [] # Gets an iterator over all users in the user collection in Firestore query = db.collection(User.__collection_name).stream() for user in query: # Adds just the email, first name, last name, last activity, and admin status to the output users.append({ 'email': user.get('email'), 'first_name': user.get('first_name'), 'last_name': user.get('last_name'), 'admin': user.get('admin'), 'last_activity': user.get('last_activity') }) return users
def update_email(self, email): """update_email(email) Used to update the user's email, since the save function looks for the document to update by email """ # Stores the old email old_email = self.email # Updates the User object's email field self.email = email # Gets the document containing the user searching by the old email user_doc = db.collection(User.__collection_name).where( 'email', '==', old_email).get() # If the user already has a document, updates the values if user_doc: user_ref = db.collection(User.__collection_name).document( user_doc[0].id) user_ref.update({ 'email': self.email, 'password': self.password, 'salt': self.salt, 'first_name': self.first_name, 'last_name': self.last_name, 'authenticated': self.authenticated, 'admin': self.admin, 'last_activity': self.last_activity, 'favorites': self.favorites, 'history': self.history, 'temp_password': self.temp_password, 'temp_password_expire': self.temp_password_expire }) # If the user does not already have a document, creates a new document else: db.collection(User.__collection_name).add({ 'email': self.email, 'password': self.password, 'salt': self.salt, 'first_name': self.first_name, 'last_name': self.last_name, 'authenticated': self.authenticated, 'admin': self.admin, 'last_activity': self.last_activity, 'favorites': self.favorites, 'history': self.history, 'temp_password': self.temp_password, 'temp_password_expire': self.temp_password_expire })
def story_page(story, page_id): """story_page() Serves a page of a story Accessed at '/story/<story/<page_id>' via a GET or POST request """ # Gets the DocumentReference to the story document in Firestore story_ref = db.collection('stories').document(story) # Gets the DocumentSnapshot of the story document in Firestore story_doc = story_ref.get() # Checks whether or not the story exists if not story_doc.exists: abort(404) # Checks whether or not the page exists in the story if page_id not in story_doc.get('page_nodes'): abort(404) # Gets the page data for the specified page ID page = story_doc.get('page_nodes.`' + page_id + '`') # Gets whether or not the page should be displayed as a preview preview = request.args.get('preview') if preview == None: preview = False # Gets whether or not the user is a guest guest = current_user == None # Replaces user attributes in the page content with the current user's values for user_attr in allowed_user_attr: page['page_body_text'] = page['page_body_text'].replace('$$' + user_attr + '$$', 'Guest' if guest else getattr(current_user, user_attr)) # Gets whether or not the page is favorited favorited = False if not guest: for favorite in current_user.favorites: if favorite['story'] == story and favorite['page_id'] == page_id: favorited = True ############################################################################################################### # In order to log a user's history, each time they click a link when navigating a story we include the # # previous page, an ID for which history they are on (so if they have taken multiple paths through the # # story, which index in the database corresponds to the current one), and whether or not they are navigating # # forward or backwards. If a request to a story page comes from one of the ways we intend (homepage, history, # # favorites, another story page), then it will be formed as a POST request with these fields so that we know # # exactly what the user is doing. If the user reaches the page through another means (most likely copying and # # pasting the URL, i.e. a GET request), then we will treat this as the user starting a new history from # # whichever page they go to, since we don't have a history to tie this to. # ############################################################################################################### # Checks that the request comes as a POST request and includes the information for recording user history if request.method == 'POST': prev_page_id = request.form['prev_page_id'] history_id = request.form['history_id'] forward = request.form['forward'] back = prev_page_id # Checks that the user is logged in if not guest: # Records the page visit to story activity user_activity = UserActivity.get_user_activity(current_user.email) user_activity.story_activity.append({ 'timestamp': datetime.now(), 'story': story, 'page_id': page_id }) user_activity.save() # Checks if a history ID is included, if not a new history is added if history_id == '': # Checks for a matching history, to not add a duplicate history history_found = False for index, history in enumerate(current_user.history): if history['story'] == story and history['pages'][0] == page_id and len(history['pages']) == 1: history['last_updated'] = datetime.now() history_found = True history_id = index # If a matching history does not already exists, adds the root page to a new history if not history_found: new_history = {} new_history['last_updated'] = datetime.now() new_history['story'] = story new_history['pages'] = [page_id] current_user.history.append(new_history) history_id = len(current_user.history) - 1 # Saves the changes to the user current_user.save() # If a history ID is include, we will edit it according to the user's behavior else: history_id = int(history_id) history = current_user.history[history_id] # If the user is moving forwards if forward: # Checks if the current page is already included in the history, if not it is added if page_id not in current_user.history[history_id]['pages']: # Checks if the previous page the user visited is the last page recorded in the current history if prev_page_id == current_user.history[history_id]['pages'][-1]: # Appends the current page to the current history and updates the timestamp current_user.history[history_id]['pages'].append(page_id) current_user.history[history_id]['last_updated'] = datetime.now() # If the previous page is not the last page recorded, then the user is branching off from their previous # path, making a new decision. In this case, we want to copy the path up to the point of the previous page # and add the current page else: new_history = {} new_history['pages'] = [] for p in current_user.history[history_id]['pages']: new_history['pages'].append(p) if p == prev_page_id: break new_history['pages'].append(page_id) new_history['story'] = current_user.history[history_id]['story'] new_history['last_updated'] = datetime.now() current_user.history.append(new_history) history_id = len(current_user.history) - 1 # Checks that the history updated or the new history created does not match another history. If there is one, # removes the current history and switches to the matching one that already existed for index, h1 in enumerate(current_user.history): for h2 in current_user.history: if h1 != h2 and len(h1['pages']) == len(h2['pages']): history_matches = True for p in range(len(h1['pages'])): if h1['pages'][p] != h2['pages'][p]: history_matches = False if history_matches: current_user.history.remove(h2) h1['last_updated'] = datetime.now() history_id = index # If the history already contains the current page, we can just update the timestamp, but # we don't want to add a duplicate page ID else: current_user.history[history_id]['last_updated'] = datetime.now() # Saves the changes to the user current_user.save() # If the user is moving backwards else: # Add any behavior for backwards navigation here pass # Sets the back page to the previous page in the history back = None back_name = None for p in current_user.history[history_id]['pages']: if p == page_id: break back = p # Gets the page name of the page that the back button points to back_name = story_doc.get('page_nodes.`' + back + '`')['page_name'] # Returns the story_page.html template with the specified page return render_response(render_template("story_page.html", guest=guest, favorited=favorited, story=story, page=page, preview=preview, back=back, back_name=back_name, history=history_id)) # Checks that the user is logged in and not previewing the page # Adds a new history in case the user gets to this page from an external link that wouldn't include the information to append the page to a history if not preview and not guest: # Checks for a matching history, to not add a duplicate history history_found = False for history in current_user.history: if history['story'] == story and history['pages'][0] == page_id and len(history['pages']) == 1: history['last_updated'] = datetime.now() history_found = True # If a matching history does not already exists, adds the root page to a new history if not history_found: new_history = {} new_history['last_updated'] = datetime.now() new_history['story'] = story new_history['pages'] = [page_id] current_user.history.append(new_history) # Saves the changes to the user current_user.save() # Returns the story_page.html template with the specified page return render_response(render_template("story_page.html", guest=guest, favorited=favorited, story=story, page=page, preview=preview))
def story_root(story): """story_root() Serves the root page of a story Accessed at '/story/<story' via a GET request """ # Gets the DocumentReference to the story document in Firestore story_ref = db.collection('stories').document(story) # Gets the DocumentSnapshot of the story document in Firestore story_doc = story_ref.get() # Checks whether or not the story exists if not story_doc.exists: abort(404) # Gets the root page's page ID page_id = story_doc.get('root_id') # Gets the page data for the specified page ID page = story_doc.get('page_nodes.`' + page_id + '`') # Gets whether or not the page is viewed as a preview (from history page) preview = request.args.get('preview') if preview == None: preview = False # Gets whether or not the user is logged in guest = current_user == None # Replaces user attributes in the page content with the current user's values for user_attr in allowed_user_attr: page['page_body_text'] = page['page_body_text'].replace('$$' + user_attr + '$$', 'Guest' if guest else getattr(current_user, user_attr)) history_id = None if not preview and not guest: # Records the page visit to story activity user_activity = UserActivity.get_user_activity(current_user.email) user_activity.story_activity.append({ 'timestamp': datetime.now(), 'story': story, 'page_id': page_id }) user_activity.save() # Checks for a matching history, to not add a duplicate history history_found = False for index, history in enumerate(current_user.history): if history['story'] == story and history['pages'][0] == page_id and len(history['pages']) == 1: # Updates timestamp of matching history history['last_updated'] = datetime.now() history_found = True history_id = index # If a matching history does not already exists, adds the root page to a new history if not history_found: new_history = {} new_history['last_updated'] = datetime.now() new_history['story'] = story new_history['pages'] = [page_id] current_user.history.append(new_history) history_id = len(current_user.history) - 1 # Saves the changes to the user current_user.save() # Gets whether or not the page is favorited favorited = False if not guest: for favorite in current_user.favorites: if favorite['story'] == story and favorite['page_id'] == page_id: favorited = True # Returns the story_page.html template with the specified page return render_response(render_template('story_page.html', guest=guest, favorited=favorited, story=story, page=page, preview=preview, history=history_id))