diff --git a/app/corpora/routes.py b/app/corpora/routes.py index 8a3e5e66..148f5775 100644 --- a/app/corpora/routes.py +++ b/app/corpora/routes.py @@ -116,20 +116,6 @@ def analyse_corpus(corpus_id): ) -@bp.route('//generate-corpus-share-link', methods=['GET', 'POST']) -@login_required -@corpus_follower_permission_required('GENERATE_SHARE_LINK') -def generate_corpus_share_link(corpus_id): - corpus_hashid = hashids.encode(corpus_id) - data = request.get_json('data') - role_name = data['role'] - exp_data = data['expiration'] - expiration = datetime.strptime(exp_data, '%b %d, %Y') - token = current_user.generate_follow_corpus_token(corpus_hashid, role_name, expiration) - link = url_for('corpora.follow_corpus', corpus_id=corpus_id, token=token, _external=True) - return link - - @bp.route('//follow/') @login_required def follow_corpus(corpus_id, token): @@ -210,6 +196,43 @@ def build_corpus(corpus_id): return response +@bp.route('//generate-corpus-share-link', methods=['POST']) +@login_required +@corpus_follower_permission_required('GENERATE_SHARE_LINK') +@content_negotiation(consumes='application/json', produces='application/json') +def generate_corpus_share_link(corpus_id): + corpus_hashid = hashids.encode(corpus_id) + data = request.json + if not isinstance(data, dict): + abort(400) + expiration = data.get('expiration') + if not isinstance(expiration, str): + abort(400) + role_name = data.get('role') + if not isinstance(role_name, str): + abort(400) + expiration_date = datetime.strptime(expiration, '%b %d, %Y') + cfr = CorpusFollowerRole.query.filter_by(name=role_name).first() + if cfr is None: + abort(400) + token = current_user.generate_follow_corpus_token(corpus_hashid, role_name, expiration_date) + corpus_share_link = url_for( + 'corpora.follow_corpus', + corpus_id=corpus_id, + token=token, + _external=True + ) + response_data = { + 'message': 'Corpus share link generated', + 'category': 'corpus', + 'corpusShareLink': corpus_share_link + } + response = jsonify(response_data) + response.status_code = 200 + return response + + + @bp.route('//is_public', methods=['PUT']) @login_required @corpus_owner_or_admin_required @@ -405,7 +428,9 @@ def add_permission(corpus_id, follower_id): role_name = request.json if not isinstance(role_name, str): abort(400) - cfr = CorpusFollowerRole.query.filter_by(name=role_name).first_or_404() + cfr = CorpusFollowerRole.query.filter_by(name=role_name).first() + if cfr is None: + abort(400) cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404() cfa.role = cfr db.session.commit() diff --git a/app/static/js/Requests/Requests.js b/app/static/js/Requests/Requests.js index 5c1d4ceb..0504d8a0 100644 --- a/app/static/js/Requests/Requests.js +++ b/app/static/js/Requests/Requests.js @@ -11,6 +11,14 @@ Requests.JSONfetch = (input, init={}) => { fetch(input, Utils.mergeObjectsDeep(init, fixedInit)) .then( (response) => { + if (response.ok) { + resolve(response.clone()); + } else { + reject(response); + } + if (response.status === 204) { + return; + } response.json() .then( (json) => { @@ -22,11 +30,6 @@ Requests.JSONfetch = (input, init={}) => { app.flash(`[${response.status}]: ${response.statusText}`, 'error'); } ); - if (response.ok) { - resolve(response); - } else { - reject(response); - } }, (response) => { app.flash('Something went wrong', 'error'); diff --git a/app/static/js/Requests/corpora/corpora.js b/app/static/js/Requests/corpora/corpora.js index 051fb07f..34593d95 100644 --- a/app/static/js/Requests/corpora/corpora.js +++ b/app/static/js/Requests/corpora/corpora.js @@ -4,9 +4,9 @@ *****************************************************************************/ Requests.corpora = {}; -Requests.corpora.ent = {}; +Requests.corpora.entity = {}; -Requests.corpora.ent.delete = (corpusId) => { +Requests.corpora.entity.delete = (corpusId) => { let input = `/corpora/${corpusId}`; let init = { method: 'DELETE' @@ -14,7 +14,7 @@ Requests.corpora.ent.delete = (corpusId) => { return Requests.JSONfetch(input, init); }; -Requests.corpora.ent.build = (corpusId) => { +Requests.corpora.entity.build = (corpusId) => { let input = `/corpora/${corpusId}/build`; let init = { method: 'POST', @@ -22,9 +22,18 @@ Requests.corpora.ent.build = (corpusId) => { return Requests.JSONfetch(input, init); }; -Requests.corpora.ent.isPublic = {}; +Requests.corpora.entity.generateShareLink = (corpusId, role, expiration) => { + let input = `/corpora/${corpusId}/generate-corpus-share-link`; + let init = { + method: 'POST', + body: JSON.stringify({role: role, expiration: expiration}) + }; + return Requests.JSONfetch(input, init); +}; -Requests.corpora.ent.isPublic.update = (corpusId, value) => { +Requests.corpora.entity.isPublic = {}; + +Requests.corpora.entity.isPublic.update = (corpusId, value) => { let input = `/corpora/${corpusId}/is_public`; let init = { method: 'PUT', @@ -33,46 +42,5 @@ Requests.corpora.ent.isPublic.update = (corpusId, value) => { return Requests.JSONfetch(input, init); }; -Requests.corpora.ent.files = {}; -Requests.corpora.ent.files.ent = {}; -Requests.corpora.ent.files.ent.delete = (corpusId, corpusFileId) => { - let input = `/corpora/${corpusId}/files/${corpusFileId}`; - let init = { - method: 'DELETE', - }; - return Requests.JSONfetch(input, init); -}; - -Requests.corpora.ent.followers = {}; - -Requests.corpora.ent.followers.add = (corpusId, usernames) => { - let input = `/corpora/${corpusId}/followers`; - let init = { - method: 'POST', - body: JSON.stringify(usernames) - }; - return Requests.JSONfetch(input, init); -}; - -Requests.corpora.ent.followers.ent = {}; - -Requests.corpora.ent.followers.ent.delete = (corpusId, followerId) => { - let input = `/corpora/${corpusId}/followers/${followerId}`; - let init = { - method: 'DELETE', - }; - return Requests.JSONfetch(input, init); -}; - -Requests.corpora.ent.followers.ent.role = {}; - -Requests.corpora.ent.followers.ent.role.update = (corpusId, followerId, value) => { - let input = `/corpora/${corpusId}/followers/${followerId}/role`; - let init = { - method: 'PUT', - body: JSON.stringify(value) - }; - return Requests.JSONfetch(input, init); -}; diff --git a/app/static/js/Requests/corpora/files.js b/app/static/js/Requests/corpora/files.js new file mode 100644 index 00000000..9ff9ba87 --- /dev/null +++ b/app/static/js/Requests/corpora/files.js @@ -0,0 +1,15 @@ +/***************************************************************************** +* Corpora * +* Fetch requests for /corpora//files routes * +*****************************************************************************/ +Requests.corpora.entity.files = {}; + +Requests.corpora.entity.files.ent = {}; + +Requests.corpora.entity.files.ent.delete = (corpusId, corpusFileId) => { + let input = `/corpora/${corpusId}/files/${corpusFileId}`; + let init = { + method: 'DELETE', + }; + return Requests.JSONfetch(input, init); +}; diff --git a/app/static/js/Requests/corpora/followers.js b/app/static/js/Requests/corpora/followers.js new file mode 100644 index 00000000..f7f7877f --- /dev/null +++ b/app/static/js/Requests/corpora/followers.js @@ -0,0 +1,35 @@ +/***************************************************************************** +* Corpora * +* Fetch requests for /corpora//followers routes * +*****************************************************************************/ +Requests.corpora.entity.followers = {}; + +Requests.corpora.entity.followers.add = (corpusId, usernames) => { + let input = `/corpora/${corpusId}/followers`; + let init = { + method: 'POST', + body: JSON.stringify(usernames) + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.entity.followers.entity = {}; + +Requests.corpora.entity.followers.entity.delete = (corpusId, followerId) => { + let input = `/corpora/${corpusId}/followers/${followerId}`; + let init = { + method: 'DELETE', + }; + return Requests.JSONfetch(input, init); +}; + +Requests.corpora.entity.followers.entity.role = {}; + +Requests.corpora.entity.followers.entity.role.update = (corpusId, followerId, value) => { + let input = `/corpora/${corpusId}/followers/${followerId}/role`; + let init = { + method: 'PUT', + body: JSON.stringify(value) + }; + return Requests.JSONfetch(input, init); +}; diff --git a/app/static/js/ResourceDisplays/CorpusDisplay.js b/app/static/js/ResourceDisplays/CorpusDisplay.js index 07f0a9be..ef9dc8b8 100644 --- a/app/static/js/ResourceDisplays/CorpusDisplay.js +++ b/app/static/js/ResourceDisplays/CorpusDisplay.js @@ -5,7 +5,7 @@ class CorpusDisplay extends ResourceDisplay { this.displayElement .querySelector('.action-button[data-action="build-request"]') .addEventListener('click', (event) => { - Requests.corpora.corpus.build(this.corpusId); + Requests.corpora.entity.build(this.corpusId); }); } diff --git a/app/static/js/ResourceLists/CorpusFollowerList.js b/app/static/js/ResourceLists/CorpusFollowerList.js index b0b621d2..82629354 100644 --- a/app/static/js/ResourceLists/CorpusFollowerList.js +++ b/app/static/js/ResourceLists/CorpusFollowerList.js @@ -122,7 +122,7 @@ class CorpusFollowerList extends ResourceList { case 'update-role': { let followerId = listItemElement.dataset.followerId; let roleName = event.target.value; - Utils.updateCorpusFollowerRole(this.corpusId, followerId, roleName); + Requests.corpora.entity.followers.entity.role.update(this.corpusId, followerId, roleName); break; } default: { @@ -141,7 +141,7 @@ class CorpusFollowerList extends ResourceList { switch (listAction) { case 'unfollow-request': { let followerId = listItemElement.dataset.followerId; - Utils.unfollowCorpusRequest(this.corpusId, followerId); + Requests.corpora.entity.followers.entity.delete(this.corpusId, followerId); break; } case 'view': { diff --git a/app/static/js/ResourceLists/CorpusList.js b/app/static/js/ResourceLists/CorpusList.js index bf62ebed..7d431673 100644 --- a/app/static/js/ResourceLists/CorpusList.js +++ b/app/static/js/ResourceLists/CorpusList.js @@ -95,7 +95,7 @@ class CorpusList extends ResourceList { let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; switch (listAction) { case 'delete-request': { - Utils.deleteCorpusRequest(this.userId, itemId); + Requests.corpora.entity.delete(this.userId, itemId); break; } case 'view': { diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index 1a35acbc..ec16ff05 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -69,151 +69,6 @@ class Utils { return Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2)); } - static updateCorpusIsPublicRequest(corpusId, isPublic) { - return new Promise((resolve, reject) => { - let fetchRessource = `/corpora/${corpusId}/is_public`; - let fetchOptions = { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(isPublic) - }; - fetch(fetchRessource, fetchOptions) - .then( - (response) => { - if (response.ok) { - app.flash(`Corpus is now ${isPublic ? 'public' : 'private'}`, 'corpus'); - resolve(response); - } else { - app.flash(`${response.statusText}`, 'error'); - reject(response); - } - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - } - - static updateCorpusFollowerRole(corpusId, followerId, roleName) { - return new Promise((resolve, reject) => { - let fetchRessource = `/corpora/${corpusId}/followers/${followerId}/role`; - let fetchOptions = { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({role: roleName}) - }; - fetch(fetchRessource, fetchOptions) - .then( - (response) => { - if (response.ok) { - app.flash('Role updated', 'corpus'); - resolve(response); - } else { - app.flash(`${response.statusText}`, 'error'); - reject(response); - } - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - } - - static addCorpusFollowersRequest(corpusId, usernames) { - return new Promise((resolve, reject) => { - let fetchRessource = `/corpora/${corpusId}/followers/add`; - let fetchOptions = { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(usernames) - }; - fetch(fetchRessource, fetchOptions) - .then( - (response) => { - if (response.ok) { - app.flash(`${usernames.length > 1 ? 'Users are' : 'User is'} following now`, 'corpus'); - resolve(response); - } else { - app.flash(`${response.statusText}`, 'error'); - reject(response); - } - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - } - - static buildCorpusRequest(userId, corpusId) { - return new Promise((resolve, reject) => { - let corpus; - try { - corpus = app.data.users[userId].corpora[corpusId]; - } catch (error) { - corpus = {}; - } - - fetch(`/corpora/${corpusId}/build`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);} - app.flash(`Corpus "${corpus?.title}" marked for building`, 'corpus'); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - } - - static unfollowCorpusRequest(corpusId, followerId) { - return new Promise((resolve, reject) => { - let fetchRessource = `/corpora/${corpusId}/followers/${followerId}/unfollow`; - let fetchOptions = { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }; - fetch(fetchRessource, fetchOptions) - .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; @@ -326,116 +181,6 @@ class Utils { }); } - static deleteSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId) { - return new Promise((resolve, reject) => { - let spaCyNLPPipelineModel; - try { - spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId]; - } catch (error) { - spaCyNLPPipelineModel = {}; - } - - let modalElement = Utils.HTMLToElement( - ` - - ` - ); - document.querySelector('#modals').appendChild(modalElement); - let modal = M.Modal.init( - modalElement, - { - dismissible: false, - onCloseEnd: () => { - modal.destroy(); - modalElement.remove(); - } - } - ); - let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); - confirmElement.addEventListener('click', (event) => { - let spaCyNLPPipelineModelTitle = spaCyNLPPipelineModel?.title; - fetch(`/contributions/spacy-nlp-pipeline-models/${spaCyNLPPipelineModelId}`, {method: 'DELETE'}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - app.flash(`SpaCy NLP Pipeline Model "${spaCyNLPPipelineModelTitle}" marked for deletion`); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - modal.open(); - }); - } - - static deleteTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId) { - return new Promise((resolve, reject) => { - let tesseractOCRPipelineModel; - try { - tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId]; - } catch (error) { - tesseractOCRPipelineModel = {}; - } - - let modalElement = Utils.HTMLToElement( - ` - - ` - ); - document.querySelector('#modals').appendChild(modalElement); - let modal = M.Modal.init( - modalElement, - { - dismissible: false, - onCloseEnd: () => { - modal.destroy(); - modalElement.remove(); - } - } - ); - let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); - confirmElement.addEventListener('click', (event) => { - let tesseractOCRPipelineModelTitle = tesseractOCRPipelineModel?.title; - fetch(`/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}`, {method: 'DELETE'}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - app.flash(`Tesseract OCR Pipeline Model "${tesseractOCRPipelineModelTitle}" marked for deletion`); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - modal.open(); - }); - } - static deleteProfileAvatarRequest(userId) { return new Promise((resolve, reject) => { let modalElement = Utils.HTMLToElement( @@ -672,94 +417,4 @@ class Utils { modal.open(); }); } - - static updateTesseractOCRPipelineModelIsPublicRequest(tesseractOCRPipelineModelId, newIsPublicValue) { - return new Promise((resolve, reject) => { - let fetchRessource = `/contributions/tesseract-ocr-pipeline-models/${tesseractOCRPipelineModelId}/is_public`; - let fetchOptions = { - method: 'PUT', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(newIsPublicValue) - }; - fetch(fetchRessource, fetchOptions) - .then( - (response) => { - if (response.ok) { - response.json().then((data) => {app.flash(data);}); - resolve(response); - } else { - app.flash(`${response.statusText}`, 'error'); - reject(response); - } - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - } - - static updateSpaCyNLPPipelineModelIsPublicRequest(SpaCyNLPPipelineModelId, newIsPublicValue) { - return new Promise((resolve, reject) => { - let fetchRessource = `/contributions/spacy-nlp-pipeline-models/${SpaCyNLPPipelineModelId}/is_public`; - let fetchOptions = { - method: 'PUT', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(newIsPublicValue) - }; - fetch(fetchRessource, fetchOptions) - .then( - (response) => { - if (response.ok) { - response.json().then((data) => {app.flash(data);}); - resolve(response); - } else { - app.flash(`${response.statusText}`, 'error'); - reject(response); - } - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - } - - static generateCorpusShareLinkRequest(corpusId, role, expiration) { - return new Promise((resolve, reject) => { - const data = {role: role, expiration: expiration}; - fetch(`/corpora/${corpusId}/generate-corpus-share-link`, {method: 'POST', headers: {Accept: 'text/plain'}, body: JSON.stringify(data)}) - .then( - (response) => { - if (!response.ok) { - app.flash(`Something went wrong: ${response.status} ${response.statusText}`, 'error'); - reject(response); - return; - } - return response.text(); - }, - (response) => { - // Something went wrong during the HTTP request - app.flash('Something went wrong', 'error'); - reject(response); - } - ) - .then( - (corpusShareLink) => {resolve(corpusShareLink);}, - (error) => { - // Something went wrong during ReadableStream processing - app.flash('Something went wrong', 'error'); - reject(error); - } - ); - }); - } } diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index d2cd8fad..7fe9cb21 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -61,8 +61,10 @@ 'js/Requests/contributions/contributions.js', 'js/Requests/contributions/spacy_nlp_pipeline_models.js', 'js/Requests/contributions/tesseract_ocr_pipeline_models.js', - 'js/Requests/Corpora.js', - 'js/Requests/jobs/jobs.js' + 'js/Requests/corpora/corpora.js', + 'js/Requests/corpora/files.js', + 'js/Requests/corpora/followers.js', + 'js/Requests/jobs/jobs.js', %} {%- endassets %} diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 3174713f..d8577907 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -226,7 +226,7 @@ let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch'); publishingModalIsPublicSwitchElement.addEventListener('change', (event) => { let newIsPublic = publishingModalIsPublicSwitchElement.checked; - Requests.corpora.corpus.isPublic.update(corpusId, newIsPublic) + Requests.corpora.entity.isPublic.update(corpusId, newIsPublic) .catch((response) => { publishingModalIsPublicSwitchElement.checked = !newIsPublic; }); @@ -236,7 +236,7 @@ // #region delete_modal_js let deleteModalDeleteButtonElement = document.querySelector('#delete-modal-delete-button'); deleteModalDeleteButtonElement.addEventListener('click', (event) => { - Requests.corpora.corpus.delete(corpusId) + Requests.corpora.entity.delete(corpusId) .then((response) => {window.location.href = '/dashboard';}); }); // #endregion delete_modal_js @@ -280,7 +280,7 @@ inviteUserModalInviteButtonElement.addEventListener('click', (event) => { let usernames = inviteUserModalSearch.chipsData.map((chipData) => chipData.tag); - Utils.addCorpusFollowersRequest(corpusId, usernames); + Requests.corpora.entity.followers.add(corpusId, usernames); }); // #endregion invite_user_modal_js @@ -323,10 +323,13 @@ ) shareLinkModalCreateButtonElement.addEventListener('click', (event) => { - Utils.generateCorpusShareLinkRequest(corpusId, shareLinkModalCorpusFollowerRoleSelectElement.value, shareLinkModalExpirationDateDatepickerElement.value) - .then((shareLink) => { - shareLinkModalOutputContainerElement.classList.remove('hide'); - shareLinkModalOutputFieldElement.value = shareLink; + Requests.corpora.entity.generateShareLink(corpusId, shareLinkModalCorpusFollowerRoleSelectElement.value, shareLinkModalExpirationDateDatepickerElement.value) + .then((response) => { + response.json() + .then((json) => { + shareLinkModalOutputContainerElement.classList.remove('hide'); + shareLinkModalOutputFieldElement.value = json.corpusShareLink; + }); }); });