From 328f85ba52c3429f96de5236654e281996303991 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Mon, 9 Dec 2024 16:12:49 +0100 Subject: [PATCH] Implement corpora endpoint/socket.io namespace --- app/__init__.py | 3 + app/blueprints/corpora/events.py | 45 ---- app/namespaces/corpora.py | 215 ++++++++++++++++++ app/namespaces/cqi_over_sio/__init__.py | 1 + app/namespaces/jobs.py | 17 +- app/namespaces/users.py | 20 +- app/static/js/app.js | 1 + app/static/js/app/endpoints/corpora.js | 57 +++++ .../static-visualization-extension.js | 38 ++-- app/static/js/requests/corpora.js | 44 ---- app/static/js/requests/jobs.js | 30 --- .../js/resource-displays/corpus-display.js | 2 +- app/static/js/resource-lists/corpus-list.js | 4 +- app/templates/_base/scripts.html.j2 | 2 +- app/templates/corpora/corpus.html.j2 | 23 +- app/templates/corpora/public_corpus.html.j2 | 19 +- 16 files changed, 339 insertions(+), 182 deletions(-) delete mode 100644 app/blueprints/corpora/events.py create mode 100644 app/namespaces/corpora.py create mode 100644 app/static/js/app/endpoints/corpora.js delete mode 100644 app/static/js/requests/jobs.js diff --git a/app/__init__.py b/app/__init__.py index 4a198125..80f77b1b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -133,6 +133,9 @@ def create_app(config: Config = Config) -> Flask: from .namespaces.cqi_over_sio import CQiOverSocketIONamespace socketio.on_namespace(CQiOverSocketIONamespace('/cqi_over_sio')) + from .namespaces.corpora import CorporaNamespace + socketio.on_namespace(CorporaNamespace('/corpora')) + from .namespaces.jobs import JobsNamespace socketio.on_namespace(JobsNamespace('/jobs')) diff --git a/app/blueprints/corpora/events.py b/app/blueprints/corpora/events.py deleted file mode 100644 index 9a0e3a36..00000000 --- a/app/blueprints/corpora/events.py +++ /dev/null @@ -1,45 +0,0 @@ -from flask_login import current_user -from flask_socketio import join_room -from app import hashids, socketio -from app.decorators import socketio_login_required -from app.models import Corpus - - -@socketio.on('GET /corpora/') -@socketio_login_required -def get_corpus(corpus_hashid): - corpus_id = hashids.decode(corpus_hashid) - corpus = Corpus.query.get(corpus_id) - if corpus is None: - return {'options': {'status': 404, 'statusText': 'Not found'}} - if not ( - corpus.is_public - or corpus.user == current_user - or current_user.is_administrator - ): - return {'options': {'status': 403, 'statusText': 'Forbidden'}} - return { - 'body': corpus.to_json_serializable(), - 'options': { - 'status': 200, - 'statusText': 'OK', - 'headers': {'Content-Type: application/json'} - } - } - - -@socketio.on('SUBSCRIBE /corpora/') -@socketio_login_required -def subscribe_corpus(corpus_hashid): - corpus_id = hashids.decode(corpus_hashid) - corpus = Corpus.query.get(corpus_id) - if corpus is None: - return {'options': {'status': 404, 'statusText': 'Not found'}} - if not ( - corpus.is_public - or corpus.user == current_user - or current_user.is_administrator - ): - return {'options': {'status': 403, 'statusText': 'Forbidden'}} - join_room(f'/corpora/{corpus.hashid}') - return {'options': {'status': 200, 'statusText': 'OK'}} diff --git a/app/namespaces/corpora.py b/app/namespaces/corpora.py new file mode 100644 index 00000000..a51c4036 --- /dev/null +++ b/app/namespaces/corpora.py @@ -0,0 +1,215 @@ +from datetime import datetime +from flask import current_app, Flask, url_for +from flask_login import current_user +from flask_socketio import Namespace +from string import punctuation +import nltk +from app import db, hashids, socketio +from app.decorators import socketio_login_required +from app.models import Corpus, CorpusFollowerAssociation, CorpusFollowerRole + + +def _delete_corpus(app: Flask, corpus_id: int): + with app.app_context(): + corpus = Corpus.query.get(corpus_id) + corpus.delete() + db.session.commit() + + +def _build_corpus(app: Flask, corpus_id: int): + with app.app_context(): + corpus = Corpus.query.get(corpus_id) + corpus.build() + db.session.commit() + + +class CorporaNamespace(Namespace): + @socketio_login_required + def on_delete(self, corpus_hashid: str) -> dict: + if not isinstance(corpus_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + + corpus_id = hashids.decode(corpus_hashid) + + if not isinstance(corpus_id, int): + return {'status': 400, 'statusText': 'Bad Request'} + + corpus = Corpus.query.get(corpus_id) + + if corpus is None: + return {'status': 404, 'statusText': 'Not Found'} + + if not ( + corpus.user == current_user + or current_user.is_administrator + ): + return {'status': 403, 'statusText': 'Forbidden'} + + socketio.start_background_task( + _delete_corpus, + current_app._get_current_object(), + corpus_id + ) + + return { + 'body': f'Corpus "{corpus.title}" marked for deletion', + 'status': 202, + 'statusText': 'Accepted' + } + + @socketio_login_required + def on_build(self, corpus_hashid: str) -> dict: + if not isinstance(corpus_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + + corpus_id = hashids.decode(corpus_hashid) + + if not isinstance(corpus_id, int): + return {'status': 400, 'statusText': 'Bad Request'} + + corpus = Corpus.query.get(corpus_id) + + if corpus is None: + return {'status': 404, 'statusText': 'Not Found'} + + cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first() + if not ( + cfa is not None and cfa.role.has_permission('MANAGE_FILES') + or corpus.user == current_user + or current_user.is_administrator + ): + return {'status': 403, 'statusText': 'Forbidden'} + + if len(corpus.files.all()) == 0: + return {'status': 409, 'statusText': 'Conflict'} + + socketio.start_background_task( + _build_corpus, + current_app._get_current_object(), + corpus_id + ) + + return { + 'body': f'Corpus "{corpus.title}" marked for building', + 'status': 202, + 'statusText': 'Accepted' + } + + # TODO: Think about where to place this, as this does not belong here... + @socketio_login_required + def on_get_stopwords(self): + languages = [ + 'german', + 'english', + 'catalan', + 'greek', + 'spanish', + 'french', + 'italian', + 'russian', + 'chinese' + ] + + nltk.download('stopwords', quiet=True) + stopwords = { + language: nltk.corpus.stopwords.words(language) + for language in languages + } + stopwords['punctuation'] = list(punctuation) + stopwords['punctuation'] += ['—', '|', '–', '“', '„', '--'] + stopwords['user_stopwords'] = [] + + return { + 'body': stopwords, + 'status': 200, + 'statusText': 'OK' + } + + @socketio_login_required + def on_create_share_link(self, corpus_hashid: str, expiration_date: str, role_name: str) -> dict: + if not isinstance(corpus_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + + if not isinstance(expiration_date, str): + return {'status': 400, 'statusText': 'Bad Request'} + + if not isinstance(role_name, str): + return {'status': 400, 'statusText': 'Bad Request'} + + print(corpus_hashid, expiration_date, role_name) + + corpus_id = hashids.decode(corpus_hashid) + + if not isinstance(corpus_id, int): + return {'status': 400, 'statusText': 'Bad Request'} + + corpus = Corpus.query.get(corpus_id) + + if corpus is None: + return {'status': 404, 'statusText': 'Not Found'} + + cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first() + if not ( + cfa is not None and cfa.role.has_permission('MANAGE_FOLLOWERS') + or corpus.user == current_user + or current_user.is_administrator + ): + return {'status': 403, 'statusText': 'Forbidden'} + + _expiration_date = datetime.strptime(expiration_date, '%b %d, %Y') + + cfr = CorpusFollowerRole.query.filter_by(name=role_name).first() + if cfr is None: + return {'status': 400, 'statusText': 'Bad Request'} + + 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 + ) + + return { + 'body': corpus_share_link, + 'status': 200, + 'statusText': 'OK' + } + + @socketio_login_required + def on_set_is_public(corpus_hashid: str, new_value: bool) -> dict: + if not isinstance(corpus_id, str): + return {'status': 400, 'statusText': 'Bad Request'} + + if not isinstance(new_value, bool): + return {'status': 400, 'statusText': 'Bad Request'} + + corpus_id = hashids.decode(corpus_hashid) + + if not isinstance(corpus_id, int): + return {'status': 400, 'statusText': 'Bad Request'} + + corpus = Corpus.query.get(corpus_id) + + if corpus is None: + return {'status': 404, 'statusText': 'Not Found'} + + if not ( + corpus.user == current_user + or current_user.is_administrator + ): + return {'status': 403, 'statusText': 'Forbidden'} + + corpus.is_public = new_value + db.session.commit() + + return { + 'body': f'Corpus "{corpus.title}" is now {"public" if new_value else "private"}', + 'status': 200, + 'statusText': 'OK' + } diff --git a/app/namespaces/cqi_over_sio/__init__.py b/app/namespaces/cqi_over_sio/__init__.py index fbf5a0f8..8778c721 100644 --- a/app/namespaces/cqi_over_sio/__init__.py +++ b/app/namespaces/cqi_over_sio/__init__.py @@ -169,6 +169,7 @@ class CQiOverSocketIONamespace(Namespace): for param in signature(fn).parameters.values(): # Check if the parameter is optional or required + # The following is true for required parameters if param.default is param.empty: if param.name not in fn_args: return {'code': 400, 'msg': 'Bad Request'} diff --git a/app/namespaces/jobs.py b/app/namespaces/jobs.py index 53c6a2b4..3ddb7a8b 100644 --- a/app/namespaces/jobs.py +++ b/app/namespaces/jobs.py @@ -23,6 +23,9 @@ def _restart_job(app: Flask, job_id: int): class JobsNamespace(Namespace): @socketio_login_required def on_delete(self, job_hashid: str) -> dict: + if not isinstance(job_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + job_id = hashids.decode(job_hashid) if not isinstance(job_id, int): @@ -31,7 +34,7 @@ class JobsNamespace(Namespace): job = Job.query.get(job_id) if job is None: - return {'status': 404, 'statusText': 'Not found'} + return {'status': 404, 'statusText': 'Not Found'} if not ( job.user == current_user @@ -53,6 +56,9 @@ class JobsNamespace(Namespace): @socketio_admin_required def on_log(self, job_hashid: str) -> dict: + if not isinstance(job_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + job_id = hashids.decode(job_hashid) if not isinstance(job_id, int): @@ -61,7 +67,7 @@ class JobsNamespace(Namespace): job = Job.query.get(job_id) if job is None: - return {'status': 404, 'statusText': 'Not found'} + return {'status': 404, 'statusText': 'Not Found'} if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]: return {'status': 409, 'statusText': 'Conflict'} @@ -72,11 +78,14 @@ class JobsNamespace(Namespace): return { 'body': log, 'status': 200, - 'statusText': 'Forbidden' + 'statusText': 'OK' } socketio_login_required def on_restart(self, job_hashid: str) -> dict: + if not isinstance(job_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + job_id = hashids.decode(job_hashid) if not isinstance(job_id, int): @@ -85,7 +94,7 @@ class JobsNamespace(Namespace): job = Job.query.get(job_id) if job is None: - return {'status': 404, 'statusText': 'Not found'} + return {'status': 404, 'statusText': 'Not Found'} if not ( job.user == current_user diff --git a/app/namespaces/users.py b/app/namespaces/users.py index 03f017ef..5a0bc014 100644 --- a/app/namespaces/users.py +++ b/app/namespaces/users.py @@ -16,6 +16,9 @@ def _delete_user(app: Flask, user_id: int): class UsersNamespace(Namespace): @socketio_login_required def on_get(self, user_hashid: str) -> dict: + if not isinstance(user_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + user_id = hashids.decode(user_hashid) if not isinstance(user_id, int): @@ -24,7 +27,7 @@ class UsersNamespace(Namespace): user = User.query.get(user_id) if user is None: - return {'status': 404, 'statusText': 'Not found'} + return {'status': 404, 'statusText': 'Not Found'} if not ( user == current_user @@ -43,6 +46,9 @@ class UsersNamespace(Namespace): @socketio_login_required def on_subscribe(self, user_hashid: str) -> dict: + if not isinstance(user_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + user_id = hashids.decode(user_hashid) if not isinstance(user_id, int): @@ -51,7 +57,7 @@ class UsersNamespace(Namespace): user = User.query.get(user_id) if user is None: - return {'status': 404, 'statusText': 'Not found'} + return {'status': 404, 'statusText': 'Not Found'} if not ( user == current_user @@ -65,6 +71,9 @@ class UsersNamespace(Namespace): @socketio_login_required def on_unsubscribe(self, user_hashid: str) -> dict: + if not isinstance(user_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + user_id = hashids.decode(user_hashid) if not isinstance(user_id, int): @@ -73,7 +82,7 @@ class UsersNamespace(Namespace): user = User.query.get(user_id) if user is None: - return {'status': 404, 'statusText': 'Not found'} + return {'status': 404, 'statusText': 'Not Found'} if not ( user == current_user @@ -87,6 +96,9 @@ class UsersNamespace(Namespace): @socketio_login_required def on_delete(self, user_hashid: str) -> dict: + if not isinstance(user_hashid, str): + return {'status': 400, 'statusText': 'Bad Request'} + user_id = hashids.decode(user_hashid) if not isinstance(user_id, int): @@ -95,7 +107,7 @@ class UsersNamespace(Namespace): user = User.query.get(user_id) if user is None: - return {'status': 404, 'statusText': 'Not found'} + return {'status': 404, 'statusText': 'Not Found'} if not ( user == current_user diff --git a/app/static/js/app.js b/app/static/js/app.js index e9469df4..20811222 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -3,6 +3,7 @@ nopaque.App = class App { this.socket = io({transports: ['websocket'], upgrade: false}); // Endpoints + this.corpora = new nopaque.app.endpoints.Corpora(this); this.jobs = new nopaque.app.endpoints.Jobs(this); this.users = new nopaque.app.endpoints.Users(this); diff --git a/app/static/js/app/endpoints/corpora.js b/app/static/js/app/endpoints/corpora.js new file mode 100644 index 00000000..20349540 --- /dev/null +++ b/app/static/js/app/endpoints/corpora.js @@ -0,0 +1,57 @@ +nopaque.app.endpoints.Corpora = class Corpora { + constructor(app) { + this.app = app; + + this.socket = io('/corpora', {transports: ['websocket'], upgrade: false}); + } + + async delete(id) { + const response = await this.socket.emitWithAck('delete', id); + + if (response.status != 202) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } + + async build(id) { + const response = await this.socket.emitWithAck('build', id); + + if (response.status != 202) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } + + async getStopwords() { + const response = await this.socket.emitWithAck('get_stopwords'); + + if (response.status != 200) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } + + async createShareLink(id, expirationDate, roleName) { + const response = await this.socket.emitWithAck('create_share_link', id, expirationDate, roleName); + + if (response.status != 200) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } + + async setIsPublic(id, newValue) { + const response = await this.socket.emitWithAck('set_is_public', id, newValue); + + if (response.status != 200) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } +} diff --git a/app/static/js/corpus-analysis/static-visualization-extension.js b/app/static/js/corpus-analysis/static-visualization-extension.js index 2deb728b..2e81e765 100644 --- a/app/static/js/corpus-analysis/static-visualization-extension.js +++ b/app/static/js/corpus-analysis/static-visualization-extension.js @@ -7,7 +7,6 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization stopwords: undefined, originalStopwords: {}, stopwordCache: {}, - promises: {getStopwords: undefined}, tokenSet: new Set() }; @@ -73,22 +72,11 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization } } - getStopwords() { - this.data.promises.getStopwords = new Promise((resolve, reject) => { - nopaque.requests.corpora.entity.getStopwords() - .then((response) => { - response.json() - .then((json) => { - this.data.originalStopwords = structuredClone(json); - this.data.stopwords = structuredClone(json); - resolve(this.data.stopwords); - }) - .catch((error) => { - reject(error); - }); - }); - }); - return this.data.promises.getStopwords; + async getStopwords() { + const stopwords = await app.corpora.getStopwords(); + this.data.originalStopwords = structuredClone(stopwords); + this.data.stopwords = structuredClone(stopwords); + return stopwords; } renderGeneralCorpusInfo() { @@ -121,7 +109,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization num_unique_pos: Object.entries(corpusData.s_attrs.text.lexicon[i].freqs.pos).length, num_unique_simple_pos: Object.entries(corpusData.s_attrs.text.lexicon[i].freqs.simple_pos).length }; - + textData.push(resource); } @@ -262,7 +250,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization } return filteredData; } - + renderFrequenciesGraphic(tokenSet) { this.data.tokenSet = tokenSet; @@ -272,7 +260,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization let texts = Object.entries(corpusData.s_attrs.text.lexicon); let graphtype = document.querySelector('.frequencies-graph-mode-button.disabled').dataset.graphType; let tokenCategory = frequenciesTokenCategoryDropdownElement.firstChild.textContent.toLowerCase(); - + let graphData = this.createFrequenciesGraphData(tokenCategory, texts, graphtype, tokenSet); let graphLayout = { barmode: graphtype === 'bar' ? 'stack' : '', @@ -328,7 +316,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization for (let i = 0; i < texts.length; i++) { tokenCountPerText[i] = (tokenCountPerText[i] || 0) + (texts[i][1].freqs[tokenCategory][originalId] || 0); } - } + } let data = { x: textTitles, y: tokenCountPerText, @@ -353,7 +341,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization stopwordLanguageChipList.innerHTML = ''; userStopwordListContainer.innerHTML = ''; stopwordInputField.value = ''; - + // Render stopword language selection. Set english as default language. Filter out user_stopwords. if (stopwordLanguageSelection.children.length === 0) { Object.keys(stopwords).forEach(language => { @@ -379,7 +367,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization // Render english stopwords as default ... let selectedLanguage = document.querySelector('#stopword-language-selection').value; this.renderStopwordLanguageChipList(selectedLanguage, stopwords[selectedLanguage]); - + // ... or render selected language stopwords. stopwordLanguageSelection.addEventListener('change', (event) => { this.renderStopwordLanguageChipList(event.target.value, stopwords[event.target.value]); @@ -402,7 +390,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization // Initialize Materialize components. M.Chips.init( - stopwordInputField, + stopwordInputField, { placeholder: 'Add stopwords', onChipAdd: (event) => { @@ -428,7 +416,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization deleteLanguageStopwordListEntriesButton.classList.toggle('disabled', stopwordLength === 0); resetLanguageStopwordListEntriesButton.classList.toggle('disabled', stopwordLength === originalStopwordListLength); } - + renderStopwordLanguageChipList(language, stopwords) { let stopwordLanguageChipList = document.querySelector('#stopword-language-chip-list'); stopwordLanguageChipList.innerHTML = ''; diff --git a/app/static/js/requests/corpora.js b/app/static/js/requests/corpora.js index beb87f84..d1adce78 100644 --- a/app/static/js/requests/corpora.js +++ b/app/static/js/requests/corpora.js @@ -5,50 +5,6 @@ nopaque.requests.corpora = {}; nopaque.requests.corpora.entity = {}; -nopaque.requests.corpora.entity.delete = (corpusId) => { - let input = `/corpora/${corpusId}`; - let init = { - method: 'DELETE' - }; - return nopaque.requests.JSONfetch(input, init); -}; - -nopaque.requests.corpora.entity.build = (corpusId) => { - let input = `/corpora/${corpusId}/build`; - let init = { - method: 'POST', - }; - return nopaque.requests.JSONfetch(input, init); -}; - -nopaque.requests.corpora.entity.generateShareLink = (corpusId, role, expiration) => { - let input = `/corpora/${corpusId}/generate-share-link`; - let init = { - method: 'POST', - body: JSON.stringify({role: role, expiration: expiration}) - }; - return nopaque.requests.JSONfetch(input, init); -}; - -nopaque.requests.corpora.entity.getStopwords = () => { - let input = `/corpora/stopwords`; - let init = { - method: 'GET' - }; - return nopaque.requests.JSONfetch(input, init); -}; - -nopaque.requests.corpora.entity.isPublic = {}; - -nopaque.requests.corpora.entity.isPublic.update = (corpusId, isPublic) => { - let input = `/corpora/${corpusId}/is_public`; - let init = { - method: 'PUT', - body: JSON.stringify(isPublic) - }; - return nopaque.requests.JSONfetch(input, init); -}; - /***************************************************************************** * Requests for /corpora//files routes * diff --git a/app/static/js/requests/jobs.js b/app/static/js/requests/jobs.js deleted file mode 100644 index 35abce32..00000000 --- a/app/static/js/requests/jobs.js +++ /dev/null @@ -1,30 +0,0 @@ -/***************************************************************************** -* Requests for /jobs routes * -*****************************************************************************/ -nopaque.requests.jobs = {}; - -nopaque.requests.jobs.entity = {}; - -nopaque.requests.jobs.entity.delete = (jobId) => { - let input = `/jobs/${jobId}`; - let init = { - method: 'DELETE' - }; - return nopaque.requests.JSONfetch(input, init); -}; - -nopaque.requests.jobs.entity.log = (jobId) => { - let input = `/jobs/${jobId}/log`; - let init = { - method: 'GET' - }; - return nopaque.requests.JSONfetch(input, init); -}; - -nopaque.requests.jobs.entity.restart = (jobId) => { - let input = `/jobs/${jobId}/restart`; - let init = { - method: 'POST' - }; - return nopaque.requests.JSONfetch(input, init); -}; diff --git a/app/static/js/resource-displays/corpus-display.js b/app/static/js/resource-displays/corpus-display.js index 82cd8897..28d7ae12 100644 --- a/app/static/js/resource-displays/corpus-display.js +++ b/app/static/js/resource-displays/corpus-display.js @@ -7,7 +7,7 @@ nopaque.resource_displays.CorpusDisplay = class CorpusDisplay extends nopaque.re this.displayElement .querySelector('.action-button[data-action="build-request"]') .addEventListener('click', (event) => { - nopaque.requests.corpora.entity.build(this.corpusId); + app.corpora.build(this.corpusId); }); } diff --git a/app/static/js/resource-lists/corpus-list.js b/app/static/js/resource-lists/corpus-list.js index ed242868..436ad2a7 100644 --- a/app/static/js/resource-lists/corpus-list.js +++ b/app/static/js/resource-lists/corpus-list.js @@ -180,7 +180,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li window.location.reload(); }); } else { - nopaque.requests.corpora.entity.delete(itemId); + app.corpora.delete(itemId); } }); modal.open(); @@ -276,7 +276,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li let listItem = this.listjs.get('id', selectedItemId)[0].elm; let values = this.listjs.get('id', listItem.dataset.id)[0].values(); if (values['is-owner']) { - nopaque.requests.corpora.entity.delete(selectedItemId); + app.corpora.delete(selectedItemId); } else { nopaque.requests.corpora.entity.followers.entity.delete(selectedItemId, currentUserId); setTimeout(() => { diff --git a/app/templates/_base/scripts.html.j2 b/app/templates/_base/scripts.html.j2 index d4eb6c7f..507c90ca 100644 --- a/app/templates/_base/scripts.html.j2 +++ b/app/templates/_base/scripts.html.j2 @@ -11,6 +11,7 @@ 'js/app.js', 'js/app/index.js', 'js/app/endpoints/index.js', + 'js/app/endpoints/corpora.js', 'js/app/endpoints/jobs.js', 'js/app/endpoints/users.js', 'js/app/extensions/index.js', @@ -51,7 +52,6 @@ 'js/requests/admin.js', 'js/requests/contributions.js', 'js/requests/corpora.js', - 'js/requests/jobs.js', 'js/requests/users.js', 'js/corpus-analysis/index.js', diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2 index 56dba7cb..73cbc75f 100644 --- a/app/templates/corpora/corpus.html.j2 +++ b/app/templates/corpora/corpus.html.j2 @@ -246,7 +246,7 @@ let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch'); publishingModalIsPublicSwitchElement.addEventListener('change', (event) => { let newIsPublic = publishingModalIsPublicSwitchElement.checked; - nopaque.requests.corpora.entity.isPublic.update({{ corpus.hashid|tojson }}, newIsPublic) + app.corpora.setIsPublic.update({{ corpus.hashid|tojson }}, newIsPublic) .catch((response) => { publishingModalIsPublicSwitchElement.checked = !newIsPublic; }); @@ -256,7 +256,7 @@ publishingModalIsPublicSwitchElement.addEventListener('change', (event) => { // #region Delete let deleteModalDeleteButtonElement = document.querySelector('#delete-modal-delete-button'); deleteModalDeleteButtonElement.addEventListener('click', (event) => { - nopaque.requests.corpora.entity.delete({{ corpus.hashid|tojson }}) + app.corpora.delete({{ corpus.hashid|tojson }}) .then((response) => { window.location.href = {{ url_for('main.dashboard')|tojson }}; }); @@ -346,19 +346,14 @@ M.Modal.init( shareLinkModalOutputContainerElement.classList.add('hide'); } } -) +); -shareLinkModalCreateButtonElement.addEventListener('click', (event) => { - let role = shareLinkModalCorpusFollowerRoleSelectElement.value; - let expiration = shareLinkModalExpirationDateDatepickerElement.value - nopaque.requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration) - .then((response) => { - response.json() - .then((json) => { - shareLinkModalOutputContainerElement.classList.remove('hide'); - shareLinkModalOutputFieldElement.value = json.corpusShareLink; - }); - }); +shareLinkModalCreateButtonElement.addEventListener('click', async (event) => { + const role = shareLinkModalCorpusFollowerRoleSelectElement.value; + const expiration = shareLinkModalExpirationDateDatepickerElement.value; + const shareLink = await app.corpora.createShareLink({{ corpus.hashid|tojson }}, expiration, role); + shareLinkModalOutputContainerElement.classList.remove('hide'); + shareLinkModalOutputFieldElement.value = shareLink; }); shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => { diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2 index a765526f..3b5fac43 100644 --- a/app/templates/corpora/public_corpus.html.j2 +++ b/app/templates/corpora/public_corpus.html.j2 @@ -273,7 +273,7 @@ publicCorpusFollowerList.add( {% if cfr.has_permission('MANAGE_FILES') %} let followerBuildRequest = document.querySelector('#follower-build-request'); followerBuildRequest.addEventListener('click', () => { - nopaque.requests.corpora.entity.build({{ corpus.hashid|tojson }}) + app.corpora.build({{ corpus.hashid|tojson }}) .then((response) => { window.location.reload(); }); @@ -380,17 +380,12 @@ M.Modal.init( } ) -shareLinkModalCreateButtonElement.addEventListener('click', (event) => { - let role = shareLinkModalCorpusFollowerRoleSelectElement.value; - let expiration = shareLinkModalExpirationDateDatepickerElement.value - nopaque.requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration) - .then((response) => { - response.json() - .then((json) => { - shareLinkModalOutputContainerElement.classList.remove('hide'); - shareLinkModalOutputFieldElement.value = json.corpusShareLink; - }); - }); +shareLinkModalCreateButtonElement.addEventListener('click', async (event) => { + const roleName = shareLinkModalCorpusFollowerRoleSelectElement.value; + const expirationDate = shareLinkModalExpirationDateDatepickerElement.value; + const shareLink = await app.corpora.createShareLink({{ corpus.hashid|tojson }}, expiration, role); + shareLinkModalOutputContainerElement.classList.remove('hide'); + shareLinkModalOutputFieldElement.value = shareLink; }); shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {