Merge branch 'public-corpus' of gitlab.ub.uni-bielefeld.de:sfb1288inf/nopaque into public-corpus

This commit is contained in:
Inga Kirschnick 2023-03-09 14:56:44 +01:00
commit 42b421c2e0
11 changed files with 130 additions and 424 deletions

View File

@ -116,20 +116,6 @@ def analyse_corpus(corpus_id):
)
@bp.route('/<hashid:corpus_id>/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('/<hashid:corpus_id>/follow/<token>')
@login_required
def follow_corpus(corpus_id, token):
@ -210,6 +196,43 @@ def build_corpus(corpus_id):
return response
@bp.route('/<hashid:corpus_id>/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('/<hashid:corpus_id>/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()

View File

@ -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');

View File

@ -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);
};

View File

@ -0,0 +1,15 @@
/*****************************************************************************
* Corpora *
* Fetch requests for /corpora/<entity>/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);
};

View File

@ -0,0 +1,35 @@
/*****************************************************************************
* Corpora *
* Fetch requests for /corpora/<entity>/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);
};

View File

@ -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);
});
}

View File

@ -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': {

View File

@ -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': {

View File

@ -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(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm SpaCy NLP Pipeline Model deletion</h4>
<p>Do you really want to delete the SpaCy NLP Pipeline Model <b>${spaCyNLPPipelineModel?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
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(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Tesseract OCR Pipeline Model deletion</h4>
<p>Do you really want to delete the Tesseract OCR Pipeline Model <b>${tesseractOCRPipelineModel?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
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);
}
);
});
}
}

View File

@ -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',
%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}

View File

@ -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;
});
});
});