Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactored ToDos of MSColab server code #2051

Merged
merged 2 commits into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions mslib/mscolab/chat_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
limitations under the License.
"""
import datetime
import os
import time

import fs
from werkzeug.utils import secure_filename

from mslib.mscolab.conf import mscolab_settings
from mslib.mscolab.models import db, Message, MessageType
Expand Down Expand Up @@ -93,3 +96,24 @@ def delete_message(self, message_id):
upload_dir.remove(fs.path.join(str(message.op_id), file_name))
db.session.delete(message)
db.session.commit()

def add_attachment(self, op_id, upload_folder, file, file_token):
with fs.open_fs('/') as home_fs:
file_dir = fs.path.join(upload_folder, str(op_id))
if '\\' not in file_dir:
if not home_fs.exists(file_dir):
home_fs.makedirs(file_dir)
else:
file_dir = file_dir.replace('\\', '/')
if not os.path.exists(file_dir):
os.makedirs(file_dir)
file_name, file_ext = file.filename.rsplit('.', 1)
file_name = f'{file_name}-{time.strftime("%Y%m%dT%H%M%S")}-{file_token}.{file_ext}'
file_name = secure_filename(file_name)
file_path = fs.path.join(file_dir, file_name)
file.save(file_path)
static_dir = fs.path.basename(upload_folder)
static_dir = static_dir.replace('\\', '/')
static_file_path = os.path.join(static_dir, str(op_id), file_name)
if os.path.exists(file_path):
return static_file_path
14 changes: 8 additions & 6 deletions mslib/mscolab/file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,11 @@ def update_operation(self, op_id, attribute, value, user):
db.session.commit()
return True

def delete_file(self, op_id, user):
def delete_operation(self, op_id, user):
"""
op_id: operation id
user: logged in user
"""
# ToDo rename to delete_operation
if self.auth_type(user.id, op_id) != "creator":
return False
Permission.query.filter_by(op_id=op_id).delete()
Expand Down Expand Up @@ -377,14 +376,18 @@ def get_all_changes(self, op_id, user, named_version=False):
'created_at': change.created_at.strftime("%Y-%m-%d, %H:%M:%S")
}, changes))

def get_change_content(self, ch_id):
def get_change_content(self, ch_id, user):
"""
ch_id: change id
user: user of this request

Get change related to id
"""
# ToDo refactor check user in op
ch = Change.query.filter_by(id=ch_id).first()
perm = Permission.query.filter_by(u_id=user.id, op_id=ch.op_id).first()
if perm is None:
return False

change = Change.query.filter_by(id=ch_id).first()
if not change:
return False
Expand All @@ -404,13 +407,12 @@ def set_version_name(self, ch_id, op_id, u_id, version_name):
db.session.commit()
return True

def undo(self, ch_id, user):
def undo_changes(self, ch_id, user):
"""
ch_id: change-id
user: user of this request

Undo a change
# ToDo rename to undo_changes
# ToDo add a revert option, which removes only that commit's change
"""
ch = Change.query.filter_by(id=ch_id).first()
Expand Down
53 changes: 16 additions & 37 deletions mslib/mscolab/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,9 @@
import functools
import json
import logging
import time
import datetime
import secrets
import fs
import os
import socketio
import sqlalchemy.exc
from itsdangerous import URLSafeTimedSerializer, BadSignature
Expand All @@ -42,7 +40,6 @@
from flask_migrate import Migrate
from flask_httpauth import HTTPBasicAuth
from validate_email import validate_email
from werkzeug.utils import secure_filename

from mslib.mscolab.conf import mscolab_settings
from mslib.mscolab.models import Change, MessageType, User, db
Expand Down Expand Up @@ -300,13 +297,12 @@ def get_user():
return json.dumps({'user': {'id': g.user.id, 'username': g.user.username}})


@APP.route("/delete_user", methods=["POST"])
@APP.route("/delete_own_account", methods=["POST"])
@verify_user
def delete_user():
def delete_own_account():
"""
delete own account
"""
# ToDo rename to delete_own_account
user = g.user
result = fm.modify_user(user, action="delete")
return jsonify({"success": result}), 200
Expand All @@ -316,7 +312,6 @@ def delete_user():
@APP.route("/messages", methods=["GET"])
@verify_user
def messages():
# ToDo maybe move is_member part to file_manager
user = g.user
op_id = request.args.get("op_id", request.form.get("op_id", None))
if fm.is_member(user.id, op_id):
Expand All @@ -336,32 +331,18 @@ def message_attachment():
file = request.files['file']
message_type = MessageType(int(request.form.get("message_type")))
user = g.user
# ToDo review
users = fm.fetch_users_without_permission(int(op_id), user.id)
if users is False:
return jsonify({"success": False, "message": "Could not send message. No file uploaded."})
if file is not None:
with fs.open_fs('/') as home_fs:
file_dir = fs.path.join(APP.config['UPLOAD_FOLDER'], op_id)
if '\\' not in file_dir:
if not home_fs.exists(file_dir):
home_fs.makedirs(file_dir)
else:
file_dir = file_dir.replace('\\', '/')
if not os.path.exists(file_dir):
os.makedirs(file_dir)
file_name, file_ext = file.filename.rsplit('.', 1)
file_name = f'{file_name}-{time.strftime("%Y%m%dT%H%M%S")}-{file_token}.{file_ext}'
file_name = secure_filename(file_name)
file_path = fs.path.join(file_dir, file_name)
file.save(file_path)
static_dir = fs.path.basename(APP.config['UPLOAD_FOLDER'])
static_dir = static_dir.replace('\\', '/')
static_file_path = os.path.join(static_dir, op_id, file_name)
new_message = cm.add_message(user, static_file_path, op_id, message_type)
new_message_dict = get_message_dict(new_message)
sockio.emit('chat-message-client', json.dumps(new_message_dict))
return jsonify({"success": True, "path": static_file_path})
static_file_path = cm.add_attachment(op_id, APP.config['UPLOAD_FOLDER'], file, file_token)
if static_file_path is not None:
new_message = cm.add_message(user, static_file_path, op_id, message_type)
new_message_dict = get_message_dict(new_message)
sockio.emit('chat-message-client', json.dumps(new_message_dict))
return jsonify({"success": True, "path": static_file_path})
else:
return "False"
return jsonify({"success": False, "message": "Could not send message. No file uploaded."})
# normal use case never gets to this
return "False"
Expand Down Expand Up @@ -430,9 +411,9 @@ def get_all_changes():
@APP.route('/get_change_content', methods=['GET'])
@verify_user
def get_change_content():
# ToDo refactor see fm.get_change_content(
ch_id = int(request.args.get('ch_id', request.form.get('ch_id', 0)))
result = fm.get_change_content(ch_id)
user = g.user
result = fm.get_change_content(ch_id, user)
if result is False:
return "False"
return jsonify({"content": result})
Expand Down Expand Up @@ -472,7 +453,7 @@ def get_operations():
def delete_operation():
op_id = int(request.form.get('op_id', 0))
user = g.user
success = fm.delete_file(op_id, user)
success = fm.delete_operation(op_id, user)
if success is False:
return jsonify({"success": False, "message": "You don't have access for this operation!"})

Expand Down Expand Up @@ -509,7 +490,6 @@ def get_operation_details():
@APP.route('/set_last_used', methods=["POST"])
@verify_user
def set_last_used():
# ToDo refactor move to file_manager
op_id = request.form.get('op_id', None)
user = g.user
days_ago = int(request.form.get('days', 0))
Expand All @@ -526,14 +506,13 @@ def set_last_used():
return jsonify({"success": True}), 200


@APP.route('/undo', methods=["POST"])
@APP.route('/undo_changes', methods=["POST"])
@verify_user
def undo_ftml():
# ToDo rename to undo_changes
def undo_changes():
ch_id = request.form.get('ch_id', -1)
ch_id = int(ch_id)
user = g.user
result = fm.undo(ch_id, user)
result = fm.undo_changes(ch_id, user)
# get op_id from change
ch = Change.query.filter_by(id=ch_id).first()
if result is True:
Expand Down
2 changes: 1 addition & 1 deletion mslib/msui/mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ def delete_account(self):
}

try:
r = requests.post(self.mscolab_server_url + '/delete_user', data=data,
r = requests.post(self.mscolab_server_url + '/delete_own_account', data=data,
timeout=tuple(config_loader(dataset="MSCOLAB_timeout")))
except requests.exceptions.RequestException as e:
logging.error(e)
Expand Down
2 changes: 1 addition & 1 deletion mslib/msui/mscolab_version_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def handle_undo(self):
"token": self.token,
"ch_id": self.changes.currentItem().id
}
url = urljoin(self.mscolab_server_url, 'undo')
url = urljoin(self.mscolab_server_url, 'undo_changes')
r = requests.post(url, data=data, timeout=tuple(config_loader(dataset="MSCOLAB_timeout")))
if r.text != "False":
# reload windows
Expand Down
19 changes: 18 additions & 1 deletion tests/_test_mscolab/test_chat_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import secrets

from flask_testing import TestCase
from werkzeug.datastructures import FileStorage

from mslib.mscolab.conf import mscolab_settings
from mslib.mscolab.models import Message, MessageType
from mslib.mscolab.models import Operation, Message, MessageType
from mslib.mscolab.mscolab import handle_db_reset
from mslib.mscolab.server import APP
from mslib.mscolab.seed import add_user, get_user, add_operation, add_user_to_operation
Expand Down Expand Up @@ -89,3 +92,17 @@ def test_delete_messages(self):
self.cm.delete_message(message.id)
message = Message.query.filter(Message.id == message.id).first()
assert message is None

def test_add_attachment(self):
sample_path = os.path.join(os.path.dirname(__file__), "..", "data")
filename = "example.csv"
name, ext = filename.split('.')
open_csv = os.path.join(sample_path, "example.csv")
operation = Operation.query.filter_by(path=self.operation_name).first()
token = secrets.token_urlsafe(16)
with open(open_csv, 'rb') as fp:
file = FileStorage(fp, filename=filename, content_type="text/csv")
static_path = self.cm.add_attachment(operation.id, mscolab_settings.UPLOAD_FOLDER, file, token)
assert name in static_path
assert static_path.endswith(ext)
assert token in static_path
13 changes: 6 additions & 7 deletions tests/_test_mscolab/test_file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,10 @@ def test_update_operation(self):
assert ren_operation.id == operation.id
assert ren_operation.path == rename_to

def test_delete_file(self):
# Todo rename "file" to operation
def test_delete_operation(self):
with self.app.test_client():
flight_path, operation = self._create_operation(flight_path='operation4')
assert self.fm.delete_file(operation.id, self.user)
assert self.fm.delete_operation(operation.id, self.user)
assert Operation.query.filter_by(path=flight_path).first() is None

def test_get_authorized_users(self):
Expand Down Expand Up @@ -279,7 +278,7 @@ def test_get_change_content(self):
assert self.fm.save_file(operation.id, self.content1, self.user)
assert self.fm.save_file(operation.id, self.content2, self.user)
all_changes = self.fm.get_all_changes(operation.id, self.user)
assert self.fm.get_change_content(all_changes[1]["id"]) == self.content1
assert self.fm.get_change_content(all_changes[1]["id"], self.user) == self.content1

def test_set_version_name(self):
with self.app.test_client():
Expand Down Expand Up @@ -307,15 +306,15 @@ def test_undo(self):
assert self.fm.save_file(operation.id, self.content2, self.user)
all_changes = self.fm.get_all_changes(operation.id, self.user)
# crestor
assert self.fm.undo(all_changes[1]["id"], self.user)
assert self.fm.undo_changes(all_changes[1]["id"], self.user)
# check collaborator
self.fm.add_bulk_permission(operation.id, self.user, [self.collaboratoruser.id], "collaborator")
assert self.fm.is_collaborator(self.collaboratoruser.id, operation.id)
assert self.fm.undo(all_changes[1]["id"], self.collaboratoruser)
assert self.fm.undo_changes(all_changes[1]["id"], self.collaboratoruser)
# check viewer
self.fm.add_bulk_permission(operation.id, self.user, [self.vieweruser.id], "viewer")
assert self.fm.is_viewer(self.vieweruser.id, operation.id)
assert self.fm.undo(all_changes[1]["id"], self.vieweruser) is False
assert self.fm.undo_changes(all_changes[1]["id"], self.vieweruser) is False

def test_fetch_users_without_permission(self):
with self.app.test_client():
Expand Down
8 changes: 4 additions & 4 deletions tests/_test_mscolab/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def test_undo(self):
changes = Change.query.filter_by(op_id=operation.id).all()
assert changes is not None
assert changes[0].id == 1
assert self.fm.undo(changes[0].id, self.user) is True
assert self.fm.undo_changes(changes[0].id, self.user) is True
assert len(self.fm.get_all_changes(operation.id, self.user)) == 3
assert "beta" in self.fm.get_file(operation.id, self.user)

Expand Down Expand Up @@ -162,9 +162,9 @@ def test_delete_operation(self):
with self.app.test_client():
self._create_operation(flight_path="f3")
op_id = get_recent_op_id(self.fm, self.user)
assert self.fm.delete_file(op_id, self.user2) is False
assert self.fm.delete_file(op_id, self.user) is True
assert self.fm.delete_file(op_id, self.user) is False
assert self.fm.delete_operation(op_id, self.user2) is False
assert self.fm.delete_operation(op_id, self.user) is True
assert self.fm.delete_operation(op_id, self.user) is False
permissions = Permission.query.filter_by(op_id=op_id).all()
assert len(permissions) == 0
operations_db = Operation.query.filter_by(id=op_id).all()
Expand Down
9 changes: 4 additions & 5 deletions tests/_test_mscolab/test_files_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,11 @@ def test_update_operation(self):
operation = Operation.query.filter_by(path=new_flight_path).first()
assert operation.description == new_description

def test_delete_file(self):
# ToDo rename to operation
def test_delete_operation(self):
with self.app.test_client():
flight_path, operation = self._create_operation(flight_path="V10")
assert operation.path == flight_path
assert self.fm.delete_file(operation.id, self.user)
assert self.fm.delete_operation(operation.id, self.user)
operation = Operation.query.filter_by(path=flight_path).first()
assert operation is None

Expand All @@ -206,9 +205,9 @@ def test_get_change_content(self):
assert self.fm.save_file(operation.id, "content2", self.user)
assert self.fm.save_file(operation.id, "content3", self.user)
all_changes = self.fm.get_all_changes(operation.id, self.user)
previous_change = self.fm.get_change_content(all_changes[2]["id"])
previous_change = self.fm.get_change_content(all_changes[2]["id"], self.user)
assert previous_change == "content1"
previous_change = self.fm.get_change_content(all_changes[1]["id"])
previous_change = self.fm.get_change_content(all_changes[1]["id"], self.user)
assert previous_change == "content2"

def test_set_version_name(self):
Expand Down
4 changes: 2 additions & 2 deletions tests/_test_mscolab/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ def test_delete_user(self):
assert add_user(self.userdata[0], self.userdata[1], self.userdata[2])
with self.app.test_client() as test_client:
token = self._get_token(test_client, self.userdata)
response = test_client.post('/delete_user', data={"token": token})
response = test_client.post('/delete_own_account', data={"token": token})
assert response.status_code == 200
data = json.loads(response.data.decode('utf-8'))
assert data["success"] is True
response = test_client.post('/delete_user', data={"token": "dsdsds"})
response = test_client.post('/delete_own_account', data={"token": "dsdsds"})
assert response.status_code == 200
assert response.data.decode('utf-8') == "False"

Expand Down
2 changes: 1 addition & 1 deletion tests/_test_msui/test_mscolab_version_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def test_version_name_delete(self, mockbox):
assert self.version_window.changes.currentItem().version_name is None

@mock.patch("PyQt5.QtWidgets.QMessageBox.question", return_value=QtWidgets.QMessageBox.Yes)
def test_undo(self, mockbox):
def test_undo_changes(self, mockbox):
self._change_version_filter(1)
# make changes
for i in range(2):
Expand Down
2 changes: 1 addition & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def mscolab_delete_user(app, msc_url, email, password):
response = mscolab_login(app, msc_url, email, password)
if response.status == '200 OK':
data = json.loads(response.get_data(as_text=True))
url = urljoin(msc_url, 'delete_user')
url = urljoin(msc_url, 'delete_own_account')
response = app.test_client().post(url, data=data)
if response.status == '200 OK':
data = json.loads(response.get_data(as_text=True))
Expand Down