diff --git a/backend/14/app.py b/backend/14/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..7456e94ce51fee4a4cc3091ae0a3f6ac8721b600
--- /dev/null
+++ b/backend/14/app.py
@@ -0,0 +1,623 @@
+import os
+import shutil
+from base64 import b64decode
+from datetime import datetime
+from subprocess import Popen, PIPE
+
+from flask import request, jsonify, json
+from flask_bcrypt import Bcrypt
+from flask_jwt_extended import JWTManager
+from flask_jwt_extended import (
+    create_access_token,
+    create_refresh_token,
+    jwt_required,
+    jwt_refresh_token_required,
+    get_jwt_identity
+)
+from simpleflock import SimpleFlock
+
+import database_helper as db
+from __init__ import app
+from socketio_helper import socketio
+
+WORKING_DIR = os.path.dirname(os.path.abspath(__file__)) + '/'
+
+# Http status codes
+OK_STATUS_CODE = 200
+BAD_REQUEST_STATUS_CODE = 400
+CONFLICT_STATUS_CODE = 409
+UNAUTHORIZED_STATUS_CODE = 401
+
+DEFAULT_EDITOR_SETTINGS = {'fontSize': 16, 'margin': 80, 'tabSize': 4}
+
+flask_bcrypt = Bcrypt(app)
+jwt = JWTManager(app)
+
+
+# GENERAL
+def generate_response(response, status, headers={}):
+    response.status_code = status
+    response.headers = {**{
+        'Content-Type': 'application/json',
+        'Access-Control-Expose-Headers': 'X-Auth-Token',
+        'Access-Control-Allow-Origin': '*'
+    }, **headers}
+    return response
+
+
+def update_project_edited(project_id):
+    project = db.get_project(project_id)
+    project.edited = datetime.now()
+    db.update_project(project)
+
+
+# Authentication
+def is_valid_credentials(username_email, password):
+    if '@' in username_email:
+        user = db.get_user_from_email(username_email)
+    else:
+        user = db.get_user_from_username(username_email)
+    return user and flask_bcrypt.check_password_hash(user.password,
+                                                     b64decode(password).decode("utf-8"))
+
+
+def is_valid_password(password):
+    return 8 <= len(password) <= 100
+
+
+def is_valid_collaborators(collaborators):
+    if not collaborators:
+        return True
+    accepted_permissions = ['View-only', 'May edit']
+    for collaborator in collaborators:
+        if (not collaborator or not db.is_user_id(collaborator['userId']) or
+                not collaborator['permission'] in accepted_permissions):
+            return False
+    return True
+
+
+@jwt.unauthorized_loader
+def unauthorized_response(callback):
+    return jsonify({
+        'ok': False,
+        'message': 'Missing Authorization Header'
+    }), 401
+
+
+@app.route('/api/auth', methods=["GET"])
+@jwt_required
+def auth():
+    return generate_response(jsonify({
+        'success': True,
+        'message': 'Authorization successful'
+    }), OK_STATUS_CODE)
+
+
+@app.route('/api/refresh', methods=['POST'])
+@jwt_refresh_token_required
+def refresh():
+    current_user = get_jwt_identity()
+    return generate_response(jsonify({
+        'success': True,
+        'message': 'Successfully refreshed token',
+        'jwt': create_access_token(identity=current_user, fresh=False)
+    }), OK_STATUS_CODE)
+
+
+# USER
+@app.route('/api/sign_up', methods=['POST'])
+def sign_up():
+    email = request.json['email']
+    username = request.json['username']
+    password = b64decode(request.json['password']).decode("utf-8")
+    if not email or not username or not password:
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'Bad request.'
+        }), BAD_REQUEST_STATUS_CODE)
+    elif not db.is_unregistered_email(email.lower()):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'User with that email already exists'
+        }), CONFLICT_STATUS_CODE)
+    elif not db.is_unregistered_username(username.lower()):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'User with that username already exists'
+        }), CONFLICT_STATUS_CODE)
+    elif not is_valid_password(password):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'Password does not fulfill requirements'
+        }), BAD_REQUEST_STATUS_CODE)
+    else:
+        new_user = db.User(
+            email.lower(),
+            username.lower(),
+            flask_bcrypt.generate_password_hash(password).decode('utf-8'))
+        db.add_user(new_user)
+
+        new_editor_settings = db.EditorSettings(
+            DEFAULT_EDITOR_SETTINGS['fontSize'],
+            DEFAULT_EDITOR_SETTINGS['margin'],
+            DEFAULT_EDITOR_SETTINGS['tabSize'])
+        db.add_editor_settings(new_editor_settings)
+
+        new_user.editorSettingsId = new_editor_settings.id
+        db.update_user(new_user.id, new_user)
+        return generate_response(db.user_schema.jsonify(new_user),
+                                 OK_STATUS_CODE)
+
+
+@app.route('/api/sign_in', methods=['GET'])
+def sign_in():
+    auth_data = request.authorization
+    username_email = auth_data.username
+    password = auth_data.password
+
+    if not username_email or not password:
+        message = 'Missing credentials!'
+        status = BAD_REQUEST_STATUS_CODE
+    elif not is_valid_credentials(username_email.lower(), password):
+        message = 'Incorrect username/email or password'
+        status = UNAUTHORIZED_STATUS_CODE
+    else:
+        if '@' in username_email:
+            username = db.get_username_from_email(username_email.lower())
+        else:
+            username = username_email
+
+        db_user = db.get_user_from_username(username.lower())
+        user = {
+            'id': db_user.id,
+            'email': db_user.email,
+            'username': db_user.username,
+            'editorSettingsId': db_user.editorSettingsId,
+            'editorSettings': db.editor_settings_schema.dumps(db_user.editorSettings),
+            'jwt': create_access_token(identity=username),
+            'refreshToken': create_refresh_token(identity=username)
+        }
+        return generate_response(jsonify(user), OK_STATUS_CODE)
+
+    return generate_response(jsonify({
+        'success': False,
+        'message': message
+    }), status)
+
+
+@app.route('/api/change_password', methods=['POST', 'PUT'])
+@jwt_required
+def change_password():
+    username = request.json['username']
+    user = db.get_user_from_username(username)
+    old_password = request.json['oldPassword']
+    new_password = request.json['newPassword']
+    if not user:
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'No user with that username'
+        }), BAD_REQUEST_STATUS_CODE)
+    elif not is_valid_credentials(username, old_password):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'Old password was incorrect'
+        }), UNAUTHORIZED_STATUS_CODE)
+    elif not is_valid_password(new_password):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'New password does not fulfill requirements'
+        }), BAD_REQUEST_STATUS_CODE)
+    else:
+        updated_user = db.change_password(
+            username,
+            flask_bcrypt.generate_password_hash(new_password).decode('utf-8')
+        )
+        return generate_response(db.user_schema.jsonify(updated_user), OK_STATUS_CODE)
+
+
+@app.route('/api/delete_account', methods=['DELETE'])
+def delete_user():
+    auth_data = request.authorization
+    username = auth_data.username
+    password = auth_data.password
+    user = db.get_user_from_username(username)
+    if not is_valid_credentials(username, password):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'Password was incorrect'
+        }), UNAUTHORIZED_STATUS_CODE)
+    elif not user:
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'No user with that username'
+        }), BAD_REQUEST_STATUS_CODE)
+    else:
+        db.delete_user_projects(user.id)
+        db.delete_user_collaborators(user.id)
+        db.delete_editor_settings(user.editorSettingsId)
+        db.delete_user(user.id)
+        return generate_response(jsonify({
+            'success': True,
+            'message': 'Successfully deleted account'
+        }), OK_STATUS_CODE)
+
+
+@app.route('/api/get_users', methods=['GET'])
+@jwt_required
+def get_users_request():
+    return generate_response(db.users_schema.jsonify(db.get_users()),
+                             OK_STATUS_CODE)
+
+
+@app.route('/api/get_user/<user_id>', methods=['GET'])
+@jwt_required
+def get_user_request(user_id):
+    return generate_response(db.user_schema.jsonify(db.get_user(user_id)),
+                             OK_STATUS_CODE)
+
+
+# EDITOR SETTINGS
+@app.route('/api/update_editor_settings/<user_id>', methods=['PUT'])
+@jwt_required
+def update_editor_settings_request(user_id):
+    editor_settings = request.json['editorSettings']
+    updated_editor_settings = db.EditorSettings(
+        editor_settings['fontSize'],
+        editor_settings['margin'],
+        editor_settings['tabSize'],
+    )
+    updated_editor_settings.id = editor_settings['id']
+    return generate_response(db.editor_settings_schema.jsonify(
+        db.update_editor_settings(updated_editor_settings)), OK_STATUS_CODE)
+
+
+# PROJECT
+@app.route('/api/add_project', methods=['POST'])
+@jwt_required
+def new_project_request():
+    title = request.json['title']
+    creator_id = request.json['creatorId']
+    collaborators = request.json['collaborators'] if request.json['collaborators'] != [] else None
+    if not title or not creator_id:
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'Bad request.'
+        }), BAD_REQUEST_STATUS_CODE)
+    elif not db.is_user_id(creator_id):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'User does not exist'
+        }), BAD_REQUEST_STATUS_CODE)
+    elif not is_valid_collaborators(collaborators):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'Invalid collaborators'
+        }), BAD_REQUEST_STATUS_CODE)
+    else:
+        new_project = db.Project(title, creator_id)
+        db.add_project(new_project)
+
+        if collaborators:
+            for collaborator in collaborators:
+                new_collaborator = db.Collaborator(
+                    project_id=new_project.id,
+                    user_id=collaborator['userId'],
+                    permission=collaborator['permission'])
+                db.add_collaborator(new_collaborator)
+
+        return generate_response(db.project_schema.jsonify(new_project), OK_STATUS_CODE)
+
+
+@app.route('/api/duplicate_project/<project_id>', methods=['POST'])
+@jwt_required
+def duplicate_project_request(project_id):
+    title = request.json['title']
+    creator_id = request.json['creatorId']
+    if not title or not creator_id:
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'Bad request.'
+        }), BAD_REQUEST_STATUS_CODE)
+    elif not db.is_user_id(creator_id):
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'User does not exist'
+        }), BAD_REQUEST_STATUS_CODE)
+    else:
+        old_project = db.get_project(project_id)
+        duplicated_project = db.Project(
+            title,
+            creator_id,
+            archived=old_project.archived
+        )
+        db.add_project(duplicated_project)
+
+        collaborators = db.get_project_collaborators(project_id)
+        if collaborators:
+            for collaborator in collaborators:
+                if collaborator['userId'] != creator_id:
+                    duplicated_collaborator = db.Collaborator(
+                        project_id=duplicated_project.id,
+                        user_id=collaborator['userId'],
+                        permission=collaborator['permission'])
+                    db.add_collaborator(duplicated_collaborator)
+
+        if old_project.creatorId != creator_id:
+            old_creator_collaborator = db.Collaborator(
+                project_id=duplicated_project.id,
+                user_id=old_project.creatorId,
+                permission='May edit')
+            db.add_collaborator(old_creator_collaborator)
+
+        files = db.get_project_files(project_id)
+        if files:
+            for file in files:
+                duplicated_file = db.File(
+                    project_id=duplicated_project.id,
+                    name=file['name'],
+                    is_folder=file['isFolder'],
+                    parent=file['parent'],
+                    content=file['content'])
+                db.add_file(duplicated_file)
+
+        return generate_response(db.project_schema.jsonify(duplicated_project), OK_STATUS_CODE)
+
+
+@app.route('/api/get_projects', methods=['GET'])
+@jwt_required
+def get_projects_request():
+    return generate_response(db.projects_schema.jsonify(db.get_projects()), OK_STATUS_CODE)
+
+
+@app.route('/api/get_projects/<user_id>', methods=['GET'])
+@jwt_required
+def get_user_projects_request(user_id):
+    return generate_response(db.projects_schema.jsonify(db.get_user_projects(user_id)),
+                             OK_STATUS_CODE)
+
+
+@app.route('/api/get_project/<project_id>', methods=['GET'])
+@jwt_required
+def get_project_request(project_id):
+    return generate_response(db.project_schema.jsonify(db.get_project(project_id)),
+                             OK_STATUS_CODE)
+
+
+@app.route('/api/update_project/<project_id>', methods=['POST', 'PUT'])
+@jwt_required
+def update_project_request(project_id):
+    project = json.loads(request.json['project'])
+    updated_project = db.Project(
+        title=project['title'],
+        creator_id=project['creatorId'],
+        archived=project['archived'])
+    updated_project.id = project['id']
+
+    return generate_response(db.project_schema.jsonify(
+        db.update_project(updated_project)), OK_STATUS_CODE)
+
+
+@app.route('/api/set_archived_project/<project_id>', methods=['POST', 'PUT'])
+@jwt_required
+def set_archived_project_request(project_id):
+    project = db.get_project(project_id)
+    project.archived = request.json['archived']
+
+    return generate_response(db.project_schema.jsonify(
+        db.update_project(project)), OK_STATUS_CODE)
+
+
+@app.route('/api/delete_project/<project_id>', methods=['DELETE'])
+@jwt_required
+def delete_project_request(project_id):
+    db.delete_project_files(project_id)
+    db.delete_project_collaborators(project_id)
+    db.delete_project_messages(project_id)
+    db.delete_project(project_id)
+    return generate_response(jsonify({
+        'success': True,
+        'message': 'Successfully deleted project'
+    }), OK_STATUS_CODE)
+
+
+@app.route('/api/parse_project_files/<project_id>', methods=['GET'])
+@jwt_required
+def parse_file_request(project_id):
+    if db.get_project(project_id):
+        if os.path.exists(str(project_id)):
+            with SimpleFlock(str(project_id) + 'lock'):
+                find_cmd = 'find ' + str(project_id) + '/ -type f -name "*.py"'
+                find = Popen(find_cmd, shell=True, stdout=PIPE)
+                files = find.communicate()[0].decode('utf-8')
+                files = ' '.join(files.splitlines())
+
+                if files:
+                    pylint_cmd = 'pylint --output-format=json ' + files
+                    pylint = Popen(pylint_cmd, shell=True, stdout=PIPE)
+                    result = pylint.communicate()[0].decode('utf-8')
+                else:
+                    result = '[]'
+        else:
+            result = '[]'
+        return generate_response(jsonify({
+            'success': True,
+            'message': 'Successfully parsed files.',
+            'result': result
+        }), OK_STATUS_CODE)
+    else:
+        return generate_response(jsonify({
+            'success': False,
+            'message': 'No such project'
+        }), BAD_REQUEST_STATUS_CODE)
+
+
+# FILE
+@app.route('/api/create_file', methods=['POST'])
+@jwt_required
+def create_file_request():
+    file = request.json['file']
+    new_file = db.File(
+        file['projectId'],
+        file['name'],
+        file['isFolder'],
+        file['parent'],
+        file['content'],
+    )
+    db.add_file(new_file)
+
+    update_project_edited(new_file.projectId)
+
+    # Add file locally on server if project is open
+    open_project_path = str(new_file.projectId) + '/'
+    if os.path.exists(open_project_path):
+        if new_file.parent:
+            file_path = open_project_path + new_file.parent + '/' + new_file.name
+        else:
+            file_path = open_project_path + new_file.name
+
+        if new_file.isFolder:
+            os.makedirs(file_path)
+        else:
+            f = open(file_path, 'w+')
+            f.write(new_file.content)
+            f.close()
+
+    app.logger.error(new_file)
+    return generate_response(db.file_schema.jsonify(
+        new_file), OK_STATUS_CODE)
+
+
+@app.route('/api/update_file/<file_id>', methods=['PUT'])
+@jwt_required
+def update_file_request(file_id):
+    file = request.json['file']
+    old_file = db.get_file(file['id'])
+
+    updated_file = db.File(
+        file['projectId'],
+        file['name'],
+        file['isFolder'],
+        file['parent'],
+        file['content'])
+    updated_file.id = file['id']
+
+    update_project_edited(updated_file.projectId)
+    project = db.get_project(updated_file.projectId)
+
+    # Update file locally on server if project is open
+    open_project_path = str(project.id) + '/'
+    if old_file.parent:
+        old_path = open_project_path + old_file.parent + '/' + old_file.name
+    else:
+        old_path = open_project_path + old_file.name
+    if os.path.exists(old_path):
+        with SimpleFlock(str(project.id) + 'lock'):
+            if os.path.exists(old_path):
+                if file['parent']:
+                    new_path = open_project_path + file['parent'] + '/' + file['name']
+                else:
+                    new_path = open_project_path + file['name']
+                os.rename(WORKING_DIR + old_path, WORKING_DIR + new_path)
+
+    return generate_response(db.file_schema.jsonify(
+        db.update_file(updated_file)), OK_STATUS_CODE)
+
+
+@app.route('/api/delete_file/<file_id>', methods=['DELETE'])
+@jwt_required
+def delete_file_request(file_id):
+    file = db.get_file(file_id)
+
+    update_project_edited(file.projectId)
+
+    # Remove file locally on server if project is open
+    open_project_path = str(file.projectId) + '/'
+    if file.parent:
+        path = open_project_path + file.parent + '/' + file.name
+    else:
+        path = open_project_path + file.name
+    if os.path.exists(path):
+        with SimpleFlock(str(file.projectId) + 'lock'):
+            if os.path.exists(path):
+                if file.isFolder:
+                    shutil.rmtree(path)
+                else:
+                    os.remove(path)
+
+    return generate_response(db.file_schema.jsonify(
+        db.delete_file(file_id)), OK_STATUS_CODE)
+
+
+@app.route('/api/get_project_files/<project_id>', methods=['GET'])
+@jwt_required
+def get_project_files_request(project_id):
+    return generate_response(db.files_schema.jsonify(
+        db.get_project_files(project_id)), OK_STATUS_CODE)
+
+
+# COLLABORATOR
+@app.route('/api/add_collaborator', methods=['POST'])
+@jwt_required
+def add_collaborator_request():
+    collaborator = request.json['collaborator']
+    new_collaborator = db.Collaborator(
+        collaborator['projectId'],
+        collaborator['userId'],
+        collaborator['permission'],
+    )
+    db.add_collaborator(new_collaborator)
+
+    update_project_edited(new_collaborator.projectId)
+
+    return generate_response(db.collaborator_schema.jsonify(
+        new_collaborator), OK_STATUS_CODE)
+
+
+@app.route('/api/update_collaborator/<collaborator_id>', methods=['PUT'])
+@jwt_required
+def update_collaborator_request(collaborator_id):
+    # Update collaborator
+    collaborator = request.json['collaborator']
+    updated_collaborator = db.Collaborator(
+        collaborator['projectId'],
+        collaborator['userId'],
+        collaborator['permission']
+    )
+    updated_collaborator.id = collaborator['id']
+
+    update_project_edited(updated_collaborator.projectId)
+
+    return generate_response(db.collaborator_schema.jsonify(
+        db.update_collaborator(updated_collaborator)), OK_STATUS_CODE)
+
+
+@app.route('/api/delete_collaborator/<collaborator_id>', methods=['DELETE'])
+@jwt_required
+def delete_collaborator_request(collaborator_id):
+    collaborator = db.get_collaborator(collaborator_id)
+    update_project_edited(collaborator.projectId)
+
+    return generate_response(db.collaborator_schema.jsonify(
+        db.delete_collaborator(collaborator_id)), OK_STATUS_CODE)
+
+
+@app.route('/api/get_project_collaborators/<project_id>', methods=['GET'])
+@jwt_required
+def get_project_collaborators_request(project_id):
+    return generate_response(db.collaborators_schema.jsonify(
+        db.get_project_collaborators(project_id)), OK_STATUS_CODE)
+
+
+# MESSAGE
+@app.route('/api/get_project_messages/<project_id>', methods=['GET'])
+@jwt_required
+def get_project_messages_request(project_id):
+    return generate_response(db.messages_schema.jsonify(
+        db.get_project_messages(project_id)), OK_STATUS_CODE)
+
+
+if __name__ == '__main__':
+    socketio.run(app, host='0.0.0.0', port=6000)
+
+    """ If you deleted the db, run the following to re-initialize it. """
+    # db.db.create_all()
diff --git a/backend/14/database_helper.py b/backend/14/database_helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6a01ae774d5da70f6fcc0cd73556f1132858bb0
--- /dev/null
+++ b/backend/14/database_helper.py
@@ -0,0 +1,517 @@
+import os
+from datetime import datetime
+
+from flask_marshmallow import Marshmallow
+from flask_sqlalchemy import SQLAlchemy
+
+from __init__ import app
+
+basedir = os.path.abspath(os.path.dirname(__file__))
+
+# Database
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'db.sqlite')
+app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+
+db = SQLAlchemy(app)
+ma = Marshmallow(app)
+
+
+# EditorSettings
+class EditorSettings(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    fontSize = db.Column(db.Integer, nullable=False)
+    margin = db.Column(db.Integer, nullable=False)
+    tabSize = db.Column(db.Integer, nullable=False)
+
+    def __init__(self, font_size, margin, tab_size):
+        self.fontSize = font_size
+        self.margin = margin
+        self.tabSize = tab_size
+
+
+class EditorSettingsSchema(ma.Schema):
+    class Meta:
+        model = EditorSettings
+        fields = ('id', 'fontSize', 'margin', 'tabSize')
+
+
+editor_settings_schema = EditorSettingsSchema(strict=True)
+
+
+# User
+class User(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    email = db.Column(db.String(100), unique=True, nullable=False)
+    username = db.Column(db.String(50), unique=True, nullable=False)
+    password = db.Column(db.Text, nullable=False)
+    editorSettingsId = db.Column(db.Integer, db.ForeignKey(EditorSettings.id))
+    editorSettings = db.relationship("EditorSettings")
+
+    def __init__(self, email, username, password):
+        self.email = email
+        self.username = username
+        self.password = password
+
+
+class UserSchema(ma.Schema):
+    editorSettings = ma.Nested(EditorSettingsSchema)
+
+    class Meta:
+        model = User
+        fields = ('id', 'email', 'username', 'editorSettingsId', 'editorSettings')
+
+
+user_schema = UserSchema(strict=True)
+users_schema = UserSchema(many=True, strict=True)
+
+
+# Project
+class Project(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    title = db.Column(db.String(50), nullable=False)
+    creatorId = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False)
+    creator = db.relationship('User')
+    created = db.Column(db.String(50), default=datetime.now, nullable=False)
+    edited = db.Column(db.String(50), onupdate=datetime.now)
+    archived = db.Column(db.Boolean, default=False, nullable=False)
+
+    def __init__(self, title, creator_id, created=None, edited=None, archived=False):
+        self.title = title
+        self.creatorId = creator_id
+        self.created = created
+        self.edited = edited
+        self.archived = archived
+
+
+class ProjectSchema(ma.Schema):
+    creator = ma.Nested(UserSchema)
+
+    class Meta:
+        model = Project
+        fields = ('id', 'title', 'creatorId', 'creator', 'created', 'edited', 'archived')
+
+
+project_schema = ProjectSchema(strict=True)
+projects_schema = ProjectSchema(many=True, strict=True)
+
+
+# File
+class File(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    projectId = db.Column(db.Integer, db.ForeignKey(Project.id), nullable=False)
+    project = db.relationship('Project', lazy='subquery')
+    name = db.Column(db.String(100), nullable=False)
+    isFolder = db.Column(db.Boolean, nullable=False)
+    parent = db.Column(db.String(100))
+    content = db.Column(db.Text)
+
+    def __init__(self, project_id, name, is_folder, parent, content):
+        self.projectId = project_id
+        self.name = name
+        self.isFolder = is_folder
+        self.parent = parent
+        self.content = content
+
+
+class FileSchema(ma.Schema):
+    project = ma.Nested(ProjectSchema)
+
+    class Meta:
+        model = File
+        fields = ('id', 'projectId', 'project', 'name', 'isFolder', 'parent', 'content')
+
+
+file_schema = FileSchema(strict=True)
+files_schema = FileSchema(many=True, strict=True)
+
+
+# Collaborator
+class Collaborator(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    projectId = db.Column(db.Integer, db.ForeignKey(Project.id), nullable=False)
+    project = db.relationship('Project', lazy='subquery')
+    userId = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False)
+    user = db.relationship('User', lazy='subquery')
+    permission = db.Column(db.Text, nullable=False)
+
+    def __init__(self, project_id, user_id, permission):
+        self.projectId = project_id
+        self.userId = user_id
+        self.permission = permission
+
+
+class CollaboratorSchema(ma.Schema):
+    project = ma.Nested(ProjectSchema)
+    user = ma.Nested(UserSchema)
+
+    class Meta:
+        fields = ('id', 'projectId', 'project', 'userId', 'user', 'permission')
+
+
+collaborator_schema = CollaboratorSchema(strict=True)
+collaborators_schema = CollaboratorSchema(many=True, strict=True)
+
+
+# Message
+class Message(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    projectId = db.Column(db.Integer, db.ForeignKey(Project.id), nullable=False)
+    project = db.relationship('Project')
+    authorId = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False)
+    author = db.relationship('User')
+    message = db.Column(db.Text)
+    time = db.Column(db.String(50), default=datetime.now, nullable=False)
+
+    def __init__(self, project_id, author_id, message, time=None):
+        self.projectId = project_id
+        self.authorId = author_id
+        self.message = message
+        self.time = time
+
+
+class MessageSchema(ma.Schema):
+    project = ma.Nested(ProjectSchema)
+    author = ma.Nested(UserSchema)
+
+    class Meta:
+        fields = ('id', 'projectId', 'project', 'authorId', 'author', 'message', 'time')
+
+
+message_schema = MessageSchema(strict=True)
+messages_schema = MessageSchema(many=True, strict=True)
+
+
+# USER
+def add_user(new_user):
+    db.session.add(new_user)
+    db.session.commit()
+
+
+def get_users():
+    all_users = User.query.all()
+    result = users_schema.dump(all_users)
+    return result.data
+
+
+def get_user(id):
+    user = User.query.get(id)
+    return user
+
+
+def update_user(id, updated_user):
+    user = User.query.get(id)
+    user.email = updated_user.email
+    user.username = updated_user.username
+    user.password = updated_user.password
+    user.editorSettingsId = updated_user.editorSettingsId
+    db.session.commit()
+    return user
+
+
+def delete_user(id):
+    user = User.query.get(id)
+    db.session.delete(user)
+    db.session.commit()
+    return user
+
+
+def delete_all_users():
+    num_users_deleted = db.session.query(User).delete()
+    db.session.commit()
+    return num_users_deleted
+
+
+def is_user(user):
+    return user and 'id' in user.keys() and bool(User.query.get(user['id']))
+
+
+def is_user_id(user_id):
+    return user_id and bool(User.query.get(user_id))
+
+
+def is_unregistered_email(email):
+    return not bool(get_user_from_email(email))
+
+
+def is_unregistered_username(username):
+    return not bool(get_user_from_username(username))
+
+
+def get_user_from_email(email):
+    return User.query.filter_by(email=email).first()
+
+
+def get_user_from_username(username):
+    return User.query.filter_by(username=username).first()
+
+
+def get_username_from_email(email):
+    return get_user_from_email(email).username
+
+
+def is_valid_email_password(email, password):
+    user = get_user_from_email(email)
+    valid_password = user.password if user else None
+    return password and password == valid_password
+
+
+def is_valid_username_password(username, password):
+    user = get_user_from_username(username)
+    valid_password = user.password if user else None
+    return password and password == valid_password
+
+
+def change_password(username, newPassword):
+    user = get_user_from_username(username)
+    user.password = newPassword
+    db.session.commit()
+    return user
+
+
+# EDITOR SETTINGS
+def add_editor_settings(editor_settings):
+    db.session.add(editor_settings)
+    db.session.commit()
+
+
+def get_editor_settings(user_id):
+    return EditorSettings.query.filter_by(userId=user_id).first()
+
+
+def update_editor_settings(updated_editor_settings):
+    editor_settings = EditorSettings.query.get(updated_editor_settings.id)
+    editor_settings.fontSize = updated_editor_settings.fontSize
+    editor_settings.margin = updated_editor_settings.margin
+    editor_settings.tabSize = updated_editor_settings.tabSize
+    db.session.commit()
+    return editor_settings
+
+
+def delete_editor_settings(editor_settings_id):
+    editor_settings = EditorSettings.query.get(editor_settings_id)
+    db.session.delete(editor_settings)
+    db.session.commit()
+    return editor_settings
+
+
+def delete_all_editor_settings():
+    num_editor_settings_deleted = db.session.query(EditorSettings).delete()
+    db.session.commit()
+    return num_editor_settings_deleted
+
+
+# PROJECT
+def add_project(project):
+    db.session.add(project)
+    db.session.commit()
+
+
+def get_projects():
+    all_projects = Project.query.all()
+    result = projects_schema.dump(all_projects)
+    return result.data
+
+
+def get_user_projects(user_id):
+    """ Returns all projects where user is the creator or a collaborator """
+    user_projects = Project.query.filter_by(creatorId=user_id).all()
+    collaborator_project_ids = [p_id for p_id, in db.session.query(Collaborator.projectId).filter_by(userId=user_id)]
+    user_projects += Project.query.filter(Project.id.in_(collaborator_project_ids)).all()
+    result = projects_schema.dump(user_projects)
+    return result.data
+
+
+def get_project(project_id):
+    project = Project.query.get(project_id)
+    return project
+
+
+def update_project(updated_project):
+    project = Project.query.get(updated_project.id)
+    project.title = updated_project.title
+    project.creatorId = updated_project.creatorId
+    project.archived = updated_project.archived
+    db.session.commit()
+    return project
+
+
+def delete_project(project_id):
+    project = Project.query.get(project_id)
+    db.session.delete(project)
+    db.session.commit()
+    return project
+
+
+def delete_user_projects(user_id):
+    """ Deletes all projects owned by user with id. """
+    user_projects_query = Project.query.filter_by(creatorId=user_id)
+
+    user_projects = user_projects_query.all()
+    for project in user_projects:
+        delete_project_files(project.id)
+        delete_project_collaborators(project.id)
+        delete_project_messages(project.id)
+        delete_project(project.id)
+
+    num_projects_deleted = user_projects_query.delete()
+    db.session.commit()
+    return num_projects_deleted
+
+
+def delete_all_projects():
+    num_projects_deleted = db.session.query(Project).delete()
+    db.session.commit()
+    return num_projects_deleted
+
+
+# FILE
+def add_file(file):
+    db.session.add(file)
+    db.session.commit()
+
+
+def get_file(id):
+    return File.query.get(id)
+
+
+def get_files():
+    all_files = File.query.all()
+    result = files_schema.dump(all_files)
+    return result.data
+
+
+def get_project_files(project_id):
+    project_files = File.query.filter_by(projectId=project_id).all()
+    result = files_schema.dump(project_files)
+    return result.data
+
+
+def update_file(updated_file):
+    file = File.query.get(updated_file.id)
+    file.projectId = updated_file.projectId
+    file.name = updated_file.name
+    file.isFolder = updated_file.isFolder
+    file.parent = updated_file.parent
+    file.content = updated_file.content
+    db.session.commit()
+    return file
+
+
+def delete_file(file_id):
+    file = File.query.filter_by(id=file_id).first()
+    db.session.delete(file)
+    db.session.commit()
+    return file
+
+
+def delete_project_files(project_id):
+    num_project_files_deleted = File.query.filter_by(projectId=project_id).delete()
+    db.session.commit()
+    return num_project_files_deleted
+
+
+def delete_all_files():
+    num_files_deleted = db.session.query(File).delete()
+    db.session.commit()
+    return num_files_deleted
+
+
+# COLLABORATOR
+def add_collaborator(collaborator):
+    db.session.add(collaborator)
+    db.session.commit()
+
+
+def get_collaborator(id):
+    collaborator = Collaborator.query.get(id)
+    return collaborator
+
+
+def get_collaborators():
+    all_collaborators = Collaborator.query.all()
+    result = collaborators_schema.dump(all_collaborators)
+    return result.data
+
+
+def get_project_collaborators(project_id):
+    project_collaborators = Collaborator.query.filter_by(projectId=project_id).all()
+    result = collaborators_schema.dump(project_collaborators)
+    return result.data
+
+
+def update_collaborator(updated_collaborator):
+    collaborator = Collaborator.query.get(updated_collaborator.id)
+    collaborator.projectId = updated_collaborator.projectId
+    collaborator.userId = updated_collaborator.userId
+    collaborator.permission = updated_collaborator.permission
+    db.session.commit()
+    return collaborator
+
+
+def delete_collaborator(collaborator_id):
+    collaborator = Collaborator.query.filter_by(id=collaborator_id).first()
+    db.session.delete(collaborator)
+    db.session.commit()
+    return collaborator
+
+
+def delete_user_collaborators(user_id):
+    num_collaborators_deleted = Collaborator.query.filter_by(userId=user_id).delete()
+    db.session.commit()
+    return num_collaborators_deleted
+
+
+def delete_project_collaborators(project_id):
+    num_collaborators_deleted = Collaborator.query.filter_by(projectId=project_id).delete()
+    db.session.commit()
+    return num_collaborators_deleted
+
+
+def delete_all_collaborators():
+    num_collaborators_deleted = db.session.query(Collaborator).delete()
+    db.session.commit()
+    return num_collaborators_deleted
+
+
+# Message
+def add_message(message):
+    db.session.add(message)
+    db.session.commit()
+
+
+def get_message(id):
+    message = Message.query.get(id)
+    return message
+
+
+def get_messages():
+    all_messages = Message.query.all()
+    result = messages_schema.dump(all_messages)
+    return result.data
+
+
+def get_project_messages(project_id):
+    project_messages = Message.query.filter_by(projectId=project_id).all()
+    result = messages_schema.dump(project_messages)
+    return result.data
+
+
+def update_message(updated_message):
+    message = Message.query.get(updated_message.id)
+    message.projectId = updated_message.projectId
+    message.authorId = updated_message.authorId
+    message.time = updated_message.time
+    db.session.commit()
+    return message
+
+
+def delete_project_messages(project_id):
+    num_project_messages_deleted = Message.query.filter_by(projectId=project_id).delete()
+    db.session.commit()
+    return num_project_messages_deleted
+
+
+def delete_all_messages():
+    num_messages_deleted = db.session.query(Message).delete()
+    db.session.commit()
+    return num_messages_deleted
diff --git a/backend/14/socketio_helper.py b/backend/14/socketio_helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c5e2db51efacf5809bb103498eec8d9fc3875fc
--- /dev/null
+++ b/backend/14/socketio_helper.py
@@ -0,0 +1,496 @@
+import math
+import os
+import shutil
+import signal
+import subprocess
+from datetime import datetime
+from subprocess import Popen, PIPE
+
+import diff_match_patch as dmp_module
+from flask import json
+from flask_socketio import SocketIO, emit, join_room, leave_room
+from simpleflock import SimpleFlock
+
+import database_helper as db
+from __init__ import app
+
+TERMINATED_NORMALLY = 0
+TERMINATED_ERROR = 1
+TERMINATED_CANCEL = 2
+
+os.environ['PYTHONUNBUFFERED'] = '1'
+socketio = SocketIO(app)
+dmp = dmp_module.diff_match_patch()
+
+project_viewers = dict()
+
+
+def has_viewers(project_id):
+    return project_viewers[str(project_id)]
+
+
+def has_viewer(username, project_id):
+    return bool(get_viewer(username, project_id))
+
+
+def get_viewer(username, project_id):
+    for viewer in project_viewers[str(project_id)]:
+        if viewer['username'] == username:
+            return viewer
+    return None
+
+
+def set_viewer(viewer, project_id):
+    filtered_viewers = []
+    for curr_viewer in project_viewers[str(project_id)]:
+        if curr_viewer['username'] != viewer['username']:
+            filtered_viewers.append(curr_viewer)
+    project_viewers[str(project_id)].append(viewer)
+    project_viewers[str(project_id)] = filtered_viewers
+
+
+def add_project_viewer(project_id, username):
+    viewer = {
+        'username': username,
+        'cursor': {
+            'fileId': None,
+            'position': None,
+            'added': False
+        },
+        'cmdFilePath': None,
+        'consoleRunPid': None
+    }
+    if not str(project_id) in project_viewers.keys():
+        project_viewers[str(project_id)] = [viewer]
+    elif not has_viewer(username, project_id):
+        project_viewers[str(project_id)].append(viewer)
+    else:
+        set_viewer(viewer, project_id)
+
+
+def remove_project_viewer(project_id, username):
+    if str(project_id) in project_viewers.keys() and \
+            has_viewer(username, project_id):
+        viewers = []
+        for viewer in project_viewers[str(project_id)]:
+            if viewer['username'] != username:
+                viewers.append(viewer)
+        project_viewers[str(project_id)] = viewers
+
+
+def is_function_def(cmd):
+    return len(cmd) > 3 and cmd[0:3] == 'def'
+
+
+@socketio.on('joinUserRoom')
+def on_join_user_room(data):
+    data = json.loads(data)
+    user_id = data['userId']
+    room = 'user' + str(user_id)
+    join_room(room)
+    emit('joinUserRoom', {
+        'on': 'joinUserRoom',
+        'userId': user_id
+    }, room=room)
+
+
+@socketio.on('leaveUserRoom')
+def on_leave_user_room(data):
+    data = json.loads(data)
+    user_id = data['userId']
+    room = 'user' + str(user_id)
+    leave_room(room)
+    emit('leaveUserRoom', {
+        'on': 'leaveUserRoom',
+        'userId': user_id
+    }, room=room)
+
+
+@socketio.on('joinProjectRoom')
+def on_join_project_room(data):
+    data = json.loads(data)
+    username = data['username']
+    project_id = data['projectId']
+    room = 'project' + str(project_id)
+    join_room(room)
+    emit('joinProjectRoom', {
+        'on': 'joinProjectRoom',
+        'username': username,
+        'projectId': project_id
+    }, room=room)
+
+
+@socketio.on('leaveProjectRoom')
+def on_leave_project_room(data):
+    data = json.loads(data)
+    username = data['username']
+    project_id = data['projectId']
+    room = 'project' + str(project_id)
+    leave_room(room)
+    emit('leaveProjectRoom', {
+        'on': 'leaveProjectRoom',
+        'username': username,
+        'projectId': project_id
+    }, room=room)
+
+
+@socketio.on('projectCreated')
+def handle_project_created(data):
+    project = json.loads(data)
+    emit('projectCreated', {
+        'on': 'projectCreated',
+        'project': project
+    }, room='project' + str(project['id']))
+
+
+@socketio.on('projectChanged')
+def handle_project_changed(data):
+    project = json.loads(data)
+    emit('projectChanged', {
+        'on': 'projectChanged',
+        'project': project
+    }, room='project' + str(project['id']))
+
+
+@socketio.on('projectDeleted')
+def handle_project_deleted(data):
+    project_id = json.loads(data)
+    emit('projectDeleted', {
+        'on': 'projectDeleted',
+        'projectId': project_id
+    }, room='project' + str(project_id))
+
+
+@socketio.on('collaboratorAdded')
+def handle_collaborator_added(data):
+    collaborator = json.loads(data)
+    emit('collaboratorAdded', {
+        'on': 'collaboratorAdded',
+        'collaborator': collaborator
+    }, room='user' + str(collaborator['userId']))
+
+
+@socketio.on('collaboratorChanged')
+def handle_collaborator_changed(data):
+    collaborator = json.loads(data)
+    emit('collaboratorChanged', {
+        'on': 'collaboratorChanged',
+        'collaborator': collaborator
+    }, room='project' + str(collaborator['projectId']))
+
+
+@socketio.on('joinOpenProjectRoom')
+def on_join_open_project_room(data):
+    data = json.loads(data)
+    project_id = data['projectId']
+    username = data['username']
+    room = 'openProject' + str(project_id)
+    join_room(room)
+    add_project_viewer(project_id, username)
+    if not os.path.exists(str(project_id)):
+        with SimpleFlock(str(project_id) + 'lock'):
+            if not os.path.exists(str(project_id)):
+                os.mkdir(str(project_id))
+                for file in db.get_project_files(project_id):
+                    dir_name = str(project_id) + ('/' + file['parent'] if file['parent'] else '')
+                    if not os.path.exists(dir_name):
+                        os.makedirs(dir_name)
+                    if not file['isFolder']:
+                        f = open(dir_name + '/' + file['name'], "w+")
+                        f.write(file['content'])
+                        f.close()
+    emit('joinOpenProjectRoom', {
+        'on': 'joinOpenProjectRoom',
+        'username': username,
+        'viewers': project_viewers[str(project_id)]
+    }, room=room)
+
+
+@socketio.on('leaveOpenProjectRoom')
+def on_leave_open_project_room(data):
+    data = json.loads(data)
+    username = data['username']
+    project_id = data['projectId']
+    room = 'openProject' + str(project_id)
+    leave_room(room)
+
+    viewer = get_viewer(username, project_id)
+    if viewer['cmdFilePath']:
+        os.remove(viewer['cmdFilePath'])
+    remove_project_viewer(project_id, username)
+
+    if not has_viewers(project_id) and os.path.exists(str(project_id)):
+        shutil.rmtree(str(project_id))
+
+    emit('leaveOpenProjectRoom', {
+        'on': 'leaveOpenProjectRoom',
+        'username': username,
+        'viewers': project_viewers[str(project_id)]
+    }, room=room)
+
+
+@socketio.on('fileCreated')
+def handle_file_created(data):
+    file = json.loads(data)
+    emit('fileCreated', {
+        'on': 'fileCreated',
+        'file': file
+    }, room='openProject' + str(file['projectId']))
+
+
+@socketio.on('fileChanged')
+def handle_file_changed(data):
+    file = json.loads(data)
+    emit('fileChanged', {
+        'on': 'fileChanged',
+        'file': file
+    }, room='openProject' + str(file['projectId']))
+
+
+@socketio.on('fileContentChanged')
+def handle_file_content_changed(data):
+    data = json.loads(data)
+    project_id = data['projectId']
+    file_id = data['fileId']
+    patches_text = data['patchesText']
+    patches = dmp.patch_fromText(patches_text)
+
+    file = db.get_file(file_id)
+    file.content = dmp.patch_apply(patches, file.content)[0]
+    db.update_file(file)
+
+    # Update file locally on server if project is open
+    open_project_path = str(file.projectId) + '/'
+    if os.path.exists(open_project_path):
+        if file.parent:
+            file_path = open_project_path + file.parent + '/' + file.name
+        else:
+            file_path = open_project_path + file.name
+        if os.path.exists(file_path):
+            f = open(file_path, 'w')
+            f.write(file.content)
+            f.close()
+    emit('fileContentChanged', {
+        'on': 'fileContentChanged',
+        'projectId': project_id,
+        'fileId': file_id,
+        'patchesText': patches_text
+    }, room='openProject' + str(project_id), include_self=False)
+
+
+@socketio.on('fileDeleted')
+def handle_file_deleted(data):
+    file = json.loads(data)
+    emit('fileDeleted', {
+        'on': 'fileDeleted',
+        'file': file
+    }, room='openProject' + str(file['projectId']))
+
+
+@socketio.on('message')
+def handle_message(data):
+    data = json.loads(data)
+
+    projectId = data['projectId']
+    username = data['username']
+    message = data['message']
+
+    author = db.get_user_from_username(username)
+    new_message = db.Message(
+        projectId,
+        author.id,
+        message
+    )
+    db.add_message(new_message)
+
+    emit('message', {
+        'on': 'message',
+        'author': db.user_schema.dumps(author),
+        'time': new_message.time,
+        'message': message
+    }, room='openProject' + str(projectId))
+
+
+@socketio.on('viewerCursorChanged')
+def handle_viewer_cursor_changed(data):
+    data = json.loads(data)
+    project_id = data['projectId']
+    file_id = data['fileId']
+    username = data['username']
+    cursor_position = data['cursorPosition']
+
+    if not str(project_id) in project_viewers.keys() \
+            or not has_viewer(username, project_id):
+        add_project_viewer(project_id, username)
+
+    for viewer in project_viewers[str(project_id)]:
+        if viewer['username'] == username:
+            viewer['cursor'] = {
+                'fileId': file_id,
+                'position': cursor_position
+            }
+    emit('viewerCursorChanged', {
+        'on': 'viewerCursorChanged',
+        'viewer': username,
+        'fileId': file_id,
+        'cursorPosition': cursor_position
+    }, room='openProject' + str(project_id), include_self=False)
+
+
+@socketio.on('lintResult')
+def handle_lint_result(data):
+    data = json.loads(data)
+    project_id = data['projectId']
+    emit('lintResult', {
+        'on': 'lintResult',
+        'projectId': project_id,
+        'lintResult': data['lintResult'],
+    }, room='openProject' + str(project_id), include_self=False)
+
+
+@socketio.on('runCmd')
+def run_cmd(data):
+    data = json.loads(data)
+    cmd = data['cmd']
+    user_id = data['userId']
+    project_id = data['projectId']
+
+    user = db.get_user(user_id)
+    viewer = get_viewer(user.username, project_id)
+    if not viewer['cmdFilePath']:
+        viewer['cmdFilePath'] = 'temp/runCmd' + user.username + \
+                                str(math.floor(datetime.now().timestamp())) + '.py'
+
+    f = open(viewer['cmdFilePath'], 'a+')
+    f.seek(0)
+    initial_content = f.read()
+    reset_content = False
+    f.write(cmd)
+    f.close()
+
+    p = subprocess.Popen(['python3', viewer['cmdFilePath']],
+                         stdout=PIPE, stderr=PIPE, preexec_fn=os.setsid)
+    viewer['consoleRunPid'] = p.pid
+    terminated_normally = True
+
+    while True:
+        line = p.stdout.readline().decode('utf-8')
+        if line:
+            emit('consoleOutput', {
+                'on': 'consoleOutput',
+                'consoleOutput': line.rstrip(),
+                'successful': True
+            }, room='user' + str(user_id))
+        else:
+            while True:
+                err = p.stderr.readline().decode('utf-8')
+                if err:
+                    terminated_normally = False
+                    if not reset_content:
+                        f = open(viewer['cmdFilePath'], 'w+')
+                        f.write(initial_content)
+                        f.close()
+                        reset_content = True
+                    emit('consoleOutput', {
+                        'on': 'consoleOutput',
+                        'consoleOutput': err.rstrip(),
+                        'successful': False
+                    }, room='user' + str(user_id))
+                else:
+                    break
+            break
+
+    if not reset_content and not is_function_def(cmd):
+        f = open(viewer['cmdFilePath'], 'r+')
+        lines = f.readlines()
+        f.truncate(0)
+        f.close()
+
+        f = open(viewer['cmdFilePath'], 'a+')
+        for line in lines:
+            if line.lstrip()[0:6] == 'print(':
+                num_leading_whitespaces = len(line) - len(line.lstrip(' '))
+                f.write((num_leading_whitespaces * ' ') + 'pass\n')
+            else:
+                f.write(line)
+        f.close()
+
+    viewer['consoleRunPid'] = None
+    emit('runningDone', {
+        'on': 'runningDone',
+        'terminatedNormally': terminated_normally
+    }, room='user' + str(user_id))
+
+
+@socketio.on('runFile')
+def run_file(data):
+    data = json.loads(data)
+    file = data['file']
+    user_id = data['userId']
+    project_id = file['projectId']
+    file_path = str(project_id) + '/'
+    file_path += file['parent'] + '/' + file['name'] if file['parent'] else file['name']
+
+    p = Popen(['python3', file_path],
+              stdout=PIPE, stderr=PIPE, preexec_fn=os.setsid)
+    user = db.get_user(user_id)
+    viewer = get_viewer(user.username, project_id)
+    viewer['consoleRunPid'] = p.pid
+    termination_status = TERMINATED_NORMALLY
+    while True:
+        line = p.stdout.readline().decode('utf-8')
+        if line:
+            emit('consoleOutput', {
+                'on': 'consoleOutput',
+                'consoleOutput': line.rstrip(),
+                'successful': True
+            }, room='user' + str(user_id))
+        else:
+            while True:
+                err = p.stderr.readline().decode('utf-8')
+                if err:
+                    termination_status = TERMINATED_ERROR
+                    emit('consoleOutput', {
+                        'on': 'consoleOutput',
+                        'consoleOutput': err.rstrip(),
+                        'successful': False
+                    }, room='user' + str(user_id))
+                else:
+                    break
+            break
+
+    viewer['consoleRunPid'] = None
+    emit('runningDone', {
+        'on': 'runningDone',
+        'terminationStatus': termination_status
+    }, room='user' + str(user_id))
+
+
+@socketio.on('resetConsoleFile')
+def reset_console_file(data):
+    data = json.loads(data)
+    user_id = data['userId']
+    project_id = data['projectId']
+    user = db.get_user(user_id)
+    viewer = get_viewer(user.username, project_id)
+    if viewer['cmdFilePath']:
+        os.remove(viewer['cmdFilePath'])
+        viewer['cmdFilePath'] = None
+
+
+@socketio.on('cancelRun')
+def cancel_run(data):
+    data = json.loads(data)
+    user_id = data['userId']
+    project_id = data['projectId']
+    user = db.get_user(user_id)
+    viewer = get_viewer(user.username, project_id)
+
+    if viewer['consoleRunPid']:
+        os.killpg(os.getpgid(viewer['consoleRunPid']), signal.SIGTERM)
+        viewer['consoleRunPid'] = None
+
+    emit('runningDone', {
+        'on': 'runningDone',
+        'terminationStatus': TERMINATED_CANCEL
+    }, room='user' + str(user_id))
diff --git a/backend/14/src_/__init__.py b/backend/14/src_/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a192eb778624f2d9aaa7ab839d49027721c1c09
--- /dev/null
+++ b/backend/14/src_/__init__.py
@@ -0,0 +1,28 @@
+import datetime
+import json
+import os
+
+from bson.objectid import ObjectId
+from flask import Flask
+from flask_cors import CORS
+
+
+class JSONEncoder(json.JSONEncoder):
+    """ Extend json-encoder class """
+
+    def default(self, o):
+        if isinstance(o, ObjectId):
+            return str(o)
+        if isinstance(o, set):
+            return list(o)
+        if isinstance(o, datetime.datetime):
+            return str(o)
+        return json.JSONEncoder.default(self, o)
+
+
+app = Flask(__name__)
+app.config['SECRET_KEY'] = os.environ.get('SECRET')
+app.config['JWT_SECRET_KEY'] = os.environ.get('SECRET')
+app.config['JWT_ACCESS_TOKEN_EXPIRES'] = datetime.timedelta(minutes=10)
+app.json_encoder = JSONEncoder
+CORS(app)
diff --git a/backend/app.py b/backend/app.py
index 380ea73f37197ce7cd3682c24f7a5a9c3bdbf5b9..82a962f4c737cdf501c888eb7b6b95558e4dc8b4 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -233,6 +233,7 @@ def delete_user():
         }), BAD_REQUEST_STATUS_CODE)
     else:
         db.delete_user_projects(user.id)
+        db.delete_user_collaborators(user.id)
         db.delete_editor_settings(user.editorSettingsId)
         db.delete_user(user.id)
         return generate_response(jsonify({
diff --git a/backend/database_helper.py b/backend/database_helper.py
index ebf49366dc4afad75d57c39d4a38b2a19fd3d0ed..c6a01ae774d5da70f6fcc0cd73556f1132858bb0 100644
--- a/backend/database_helper.py
+++ b/backend/database_helper.py
@@ -455,10 +455,16 @@ def delete_collaborator(collaborator_id):
     return collaborator
 
 
+def delete_user_collaborators(user_id):
+    num_collaborators_deleted = Collaborator.query.filter_by(userId=user_id).delete()
+    db.session.commit()
+    return num_collaborators_deleted
+
+
 def delete_project_collaborators(project_id):
-    num_project_collaborators_deleted = Collaborator.query.filter_by(projectId=project_id).delete()
+    num_collaborators_deleted = Collaborator.query.filter_by(projectId=project_id).delete()
     db.session.commit()
-    return num_project_collaborators_deleted
+    return num_collaborators_deleted
 
 
 def delete_all_collaborators():
diff --git a/backend/db.sqlite b/backend/db.sqlite
index 084815e9a7d91cf91d5a97c974884aa24770b925..1d732d65c4b07ba635883e618ca1c07d86bda580 100644
Binary files a/backend/db.sqlite and b/backend/db.sqlite differ