diff --git a/.gitignore b/.gitignore index 59ada396..b7a84431 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ logs/ !logs/dummy *.env +*.pjentsch-testing + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/app/corpora/routes.py b/app/corpora/routes.py index a3aca488..8d321c6f 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -3,6 +3,7 @@ from flask import ( abort, current_app, flash, + make_response, Markup, redirect, render_template, @@ -70,49 +71,59 @@ def disable_corpus_is_public(corpus_id): # return redirect(url_for('.corpus', corpus_id=corpus_id)) -@bp.route('//unfollow', methods=['GET', 'POST']) +@bp.route('//followers//unfollow', methods=['POST']) @login_required -def unfollow_corpus(corpus_id): - corpus = Corpus.query.get_or_404(corpus_id) - user_hashid = request.args.get('user_id') - if user_hashid is None: - user = current_user - elif current_user.is_administrator(): - user_id = hashids.decode(user_hashid) - user = User.query.get_or_404(user_id) - else: +def unfollow_corpus(corpus_id, follower_id): + corpus_follower_association = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=follower_id).first_or_404() + if not (corpus_follower_association.followed_corpus.user == current_user + or corpus_follower_association.following_user == current_user + or current_user.is_administrator()): abort(403) - if user.is_following_corpus(corpus): - user.unfollow_corpus(corpus) + if not corpus_follower_association.following_user.is_following_corpus(corpus_follower_association.followed_corpus): + abort(409) # 'User is not following the corpus' + corpus_follower_association.following_user.unfollow_corpus(corpus_follower_association.followed_corpus) + db.session.commit() + return '', 204 + + +@bp.route('//unfollow', methods=['POST']) +@login_required +def current_user_unfollow_corpus(corpus_id): + corpus = Corpus.query.get_or_404(corpus_id) + if not current_user.is_following_corpus(corpus): + abort(409) # 'You are not following the corpus' + current_user.unfollow_corpus(corpus) db.session.commit() flash(f'You are not following {corpus.title} anymore', category='corpus') return '', 204 -@bp.route('//followers//permissions//add', methods=['POST']) -def add_permission(corpus_id, corpus_follower_id, permission_name): - if permission_name not in [x.name for x in CorpusFollowerPermission]: - abort(400) - corpus_follower_association = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, id=corpus_follower_id).first_or_404() - corpus = corpus_follower_association.followed_corpus - if not (corpus.user == current_user or current_user.is_administrator()): +@bp.route('//followers//permissions//add', methods=['POST']) +def add_permission(corpus_id, follower_id, permission_name): + try: + permission = CorpusFollowerPermission[permission_name] + except KeyError: + abort(409) # 'Permission "{permission_name}" does not exist' + corpus_follower_association = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=follower_id).first_or_404() + if not (corpus_follower_association.followed_corpus.user == current_user + or current_user.is_administrator()): abort(403) - permission_value = CorpusFollowerPermission[permission_name].value - corpus_follower_association.add_permission(permission_value) + corpus_follower_association.add_permission(permission) db.session.commit() return '', 204 -@bp.route('//followers//permissions//remove', methods=['POST']) -def remove_permission(corpus_id, corpus_follower_id, permission_name): - if permission_name not in [x.name for x in CorpusFollowerPermission]: - abort(400) - corpus_follower_association = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, id=corpus_follower_id).first_or_404() - corpus = corpus_follower_association.followed_corpus - if not (corpus.user == current_user or current_user.is_administrator()): +@bp.route('//followers//permissions//remove', methods=['POST']) +def remove_permission(corpus_id, follower_id, permission_name): + try: + permission = CorpusFollowerPermission[permission_name] + except KeyError: + return make_response(f'Permission "{permission_name}" does not exist', 409) + corpus_follower_association = CorpusFollowerAssociation.query.filter_by(followed_corpus_id=corpus_id, following_user_id=follower_id).first_or_404() + if not (corpus_follower_association.followed_corpus.user == current_user + or current_user.is_administrator()): abort(403) - permission_value = CorpusFollowerPermission[permission_name].value - corpus_follower_association.remove_permission(permission_value) + corpus_follower_association.remove_permission(permission) db.session.commit() return '', 204 diff --git a/app/models.py b/app/models.py index 314ebf2a..81da0eb3 100644 --- a/app/models.py +++ b/app/models.py @@ -307,23 +307,23 @@ class CorpusFollowerAssociation(HashidMixin, db.Model): def __repr__(self): return f'' - def has_permission(self, permission): - return self.permissions & permission == permission + def has_permission(self, permission: CorpusFollowerPermission): + return self.permissions & permission.value == permission.value - def add_permission(self, permission): + def add_permission(self, permission: CorpusFollowerPermission): if not self.has_permission(permission): - self.permissions += permission + self.permissions += permission.value - def remove_permission(self, permission): + def remove_permission(self, permission: CorpusFollowerPermission): if self.has_permission(permission): - self.permissions -= permission + self.permissions -= permission.value def to_json_serializeable(self, backrefs=False, relationships=False): json_serializeable = { 'id': self.hashid, 'permissions': [ x.name for x in CorpusFollowerPermission - if self.has_permission(x.value) + if self.has_permission(x) ], 'followed_corpus': self.followed_corpus.to_json_serializeable(), 'following_user': self.following_user.to_json_serializeable() @@ -370,7 +370,8 @@ class User(HashidMixin, UserMixin, db.Model): ) followed_corpus_associations = db.relationship( 'CorpusFollowerAssociation', - back_populates='following_user' + back_populates='following_user', + cascade='all, delete-orphan' ) followed_corpora = association_proxy( 'followed_corpus_associations', @@ -1320,7 +1321,8 @@ class Corpus(HashidMixin, db.Model): ) following_user_associations = db.relationship( 'CorpusFollowerAssociation', - back_populates='followed_corpus' + back_populates='followed_corpus', + cascade='all, delete-orphan' ) following_users = association_proxy( 'following_user_associations', diff --git a/app/static/js/ResourceLists/CorpusFollowerList.js b/app/static/js/ResourceLists/CorpusFollowerList.js index cc7471d0..711d5111 100644 --- a/app/static/js/ResourceLists/CorpusFollowerList.js +++ b/app/static/js/ResourceLists/CorpusFollowerList.js @@ -33,22 +33,22 @@ class CorpusFollowerList extends ResourceList {


- delete + delete send @@ -59,6 +59,7 @@ class CorpusFollowerList extends ResourceList { get valueNames() { return [ {data: ['id']}, + {data: ['follower-id']}, {name: 'avatar', attr: 'src'}, 'username', 'about-me', @@ -97,13 +98,14 @@ class CorpusFollowerList extends ResourceList { let user = corpusFollowerAssociation.following_user; return { 'id': corpusFollowerAssociation.id, + 'follower-id': user.id, 'avatar': user.avatar ? `/users/${user.id}/avatar` : '/static/images/user_avatar.png', 'username': user.username, 'full-name': user.full_name ? user.full_name : '', 'about-me': user.about_me ? user.about_me : '', - 'permissions-can-VIEW': corpusFollowerAssociation.permissions.includes('VIEW'), - 'permissions-can-CONTRIBUTE': corpusFollowerAssociation.permissions.includes('CONTRIBUTE'), - 'permissions-can-ADMINISTRATE': corpusFollowerAssociation.permissions.includes('ADMINISTRATE') + 'permission-can-VIEW': corpusFollowerAssociation.permissions.includes('VIEW'), + 'permission-can-CONTRIBUTE': corpusFollowerAssociation.permissions.includes('CONTRIBUTE'), + 'permission-can-ADMINISTRATE': corpusFollowerAssociation.permissions.includes('ADMINISTRATE') }; } @@ -120,27 +122,15 @@ class CorpusFollowerList extends ResourceList { if (listActionElement === null) {return;} let listAction = listActionElement.dataset.listAction; switch (listAction) { - case 'toggle-permission-view': { + case 'toggle-permission': { + let followerId = listItemElement.dataset.followerId; + let permission = listActionElement.dataset.permission; if (event.target.checked) { - Utils.addCorpusFollowerPermissionRequest(this.userId, this.corpusId, itemId, 'VIEW'); + Utils.addCorpusFollowerPermissionRequest(this.corpusId, followerId, permission) + .catch((error) => {event.target.checked = !event.target.checked;}); } else { - Utils.removeCorpusFollowerPermissionRequest(this.userId, this.corpusId, itemId, 'VIEW'); - } - break; - } - case 'toggle-permission-contribute': { - if (event.target.checked) { - Utils.addCorpusFollowerPermissionRequest(this.userId, this.corpusId, itemId, 'CONTRIBUTE'); - } else { - Utils.removeCorpusFollowerPermissionRequest(this.userId, this.corpusId, itemId, 'CONTRIBUTE'); - } - break; - } - case 'toggle-permission-administrate': { - if (event.target.checked) { - Utils.addCorpusFollowerPermissionRequest(this.userId, this.corpusId, itemId, 'ADMINISTRATE'); - } else { - Utils.removeCorpusFollowerPermissionRequest(this.userId, this.corpusId, itemId, 'ADMINISTRATE'); + Utils.removeCorpusFollowerPermissionRequest(this.corpusId, followerId, permission) + .catch((error) => {event.target.checked = !event.target.checked;}); } break; } @@ -151,7 +141,26 @@ class CorpusFollowerList extends ResourceList { } onClick(event) { - + let listItemElement = event.target.closest('.list-item[data-id]'); + if (listItemElement === null) {return;} + let itemId = listItemElement.dataset.id; + let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); + let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; + switch (listAction) { + case 'unfollow-request': { + let followerId = listItemElement.dataset.followerId; + Utils.unfollowCorpusRequest(this.corpusId, followerId); + break; + } + case 'view': { + let followerId = listItemElement.dataset.followerId; + window.location.href = `/users/${followerId}`; + break; + } + default: { + break; + } + } } onPatch(patch) {} diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index af72bbe0..547f29b5 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -69,16 +69,19 @@ class Utils { return Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2)); } - static addCorpusFollowerPermissionRequest(userId, corpusId, corpusFollowerAssociationId, permission) { + static addCorpusFollowerPermissionRequest(corpusId, followerId, permission) { return new Promise((resolve, reject) => { - fetch(`/corpora/${corpusId}/followers/${corpusFollowerAssociationId}/permissions/${permission}/add`, {method: 'POST', headers: {Accept: 'application/json'}}) + fetch(`/corpora/${corpusId}/followers/${followerId}/permissions/${permission}/add`, {method: 'POST', headers: {Accept: 'application/json'}}) .then( (response) => { - if (response.status === 400) {app.flash('Bad Request', 'error'); reject(response);} - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - app.flash(`Permission added`, 'corpus'); - resolve(response); + if (response.ok) { + app.flash(`Permission added`, 'corpus'); + resolve(response); + return; + } else { + app.flash(`${response.statusText}`, 'error'); + reject(response); + } }, (response) => { app.flash('Something went wrong', 'error'); @@ -88,16 +91,18 @@ class Utils { }); } - static removeCorpusFollowerPermissionRequest(userId, corpusId, corpusFollowerAssociationId, permission) { + static removeCorpusFollowerPermissionRequest(corpusId, followerId, permission) { return new Promise((resolve, reject) => { - fetch(`/corpora/${corpusId}/followers/${corpusFollowerAssociationId}/permissions/${permission}/remove`, {method: 'POST', headers: {Accept: 'application/json'}}) + fetch(`/corpora/${corpusId}/followers/${followerId}/permissions/${permission}/remove`, {method: 'POST', headers: {Accept: 'application/json'}}) .then( (response) => { - if (response.status === 400) {app.flash('Bad Request', 'error'); reject(response);} - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - app.flash(`Permission removed`, 'corpus'); - resolve(response); + if (response.ok) { + app.flash(`Permission removed`, 'corpus'); + resolve(response); + } else { + app.flash(`${response.statusText}`, 'error'); + reject(response); + } }, (response) => { app.flash('Something went wrong', 'error'); @@ -215,6 +220,27 @@ class Utils { }); } + static unfollowCorpusRequest(corpusId, followerId) { + return new Promise((resolve, reject) => { + fetch(`/corpora/${corpusId}/followers/${followerId}/unfollow`, {method: 'POST', headers: {Accept: 'application/json'}}) + .then( + (response) => { + if (response.ok) { + app.flash(`User unfollowed from Corpus`, 'corpus'); + resolve(response); + } else { + app.flash(`${response.statusText}`, 'error'); + reject(response); + } + }, + (response) => { + app.flash('Something went wrong', 'error'); + reject(response); + } + ); + }); + } + static deleteCorpusRequest(userId, corpusId) { return new Promise((resolve, reject) => { let corpus; diff --git a/app/static/js_new/App.js b/app/static/js_new/App.js deleted file mode 100644 index 8356642d..00000000 --- a/app/static/js_new/App.js +++ /dev/null @@ -1,10 +0,0 @@ -import DataStore from './DataStore'; -import EventBroker from './EventBroker'; - - -const dataStore = new DataStore(); -const eventBroker = new EventBroker(); -const socket = io({transports: ['websocket'], upgrade: false}); - - -export {eventBroker, socket};