mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-24 10:34:17 +00:00
Change the user session SocketIO Logic
This commit is contained in:
parent
b8bf004684
commit
76924956de
@ -2,4 +2,4 @@ from flask import Blueprint
|
|||||||
|
|
||||||
|
|
||||||
bp = Blueprint('main', __name__)
|
bp = Blueprint('main', __name__)
|
||||||
from . import events, routes
|
from . import routes
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
from app import hashids, socketio
|
|
||||||
from app.models import User
|
|
||||||
from flask_login import current_user
|
|
||||||
from flask_socketio import join_room
|
|
||||||
from app.decorators import socketio_login_required
|
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('users.user.get')
|
|
||||||
@socketio_login_required
|
|
||||||
def users_user_get(user_hashid):
|
|
||||||
user_id = hashids.decode(user_hashid)
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
if user is None:
|
|
||||||
return {'code': 404, 'msg': 'Not found'}
|
|
||||||
if not (user == current_user or current_user.is_administrator):
|
|
||||||
return {'code': 403, 'msg': 'Forbidden'}
|
|
||||||
# corpora = [x.to_dict() for x in user.corpora]
|
|
||||||
# jobs = [x.to_dict() for x in user.jobs]
|
|
||||||
# transkribus_htr_models = TranskribusHTRModel.query.filter(
|
|
||||||
# (TranskribusHTRModel.shared == True) | (TranskribusHTRModel.user == user)
|
|
||||||
# ).all()
|
|
||||||
# tesseract_ocr_models = TesseractOCRModel.query.filter(
|
|
||||||
# (TesseractOCRModel.shared == True) | (TesseractOCRModel.user == user)
|
|
||||||
# ).all()
|
|
||||||
# response = {
|
|
||||||
# 'code': 200,
|
|
||||||
# 'msg': 'OK',
|
|
||||||
# 'payload': {
|
|
||||||
# 'user': user.to_dict(),
|
|
||||||
# 'corpora': corpora,
|
|
||||||
# 'jobs': jobs,
|
|
||||||
# 'transkribus_htr_models': transkribus_htr_models,
|
|
||||||
# 'tesseract_ocr_models': tesseract_ocr_models
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
join_room(f'users.{user.hashid}')
|
|
||||||
return {
|
|
||||||
'code': 200,
|
|
||||||
'msg': 'OK',
|
|
||||||
'payload': user.to_dict(backrefs=True, relationships=True)
|
|
||||||
}
|
|
@ -1033,6 +1033,8 @@ def ressource_after_delete(mapper, connection, ressource):
|
|||||||
jsonpatch = [{'op': 'remove', 'path': ressource.jsonpatch_path}]
|
jsonpatch = [{'op': 'remove', 'path': ressource.jsonpatch_path}]
|
||||||
room = f'users.{ressource.user_hashid}'
|
room = f'users.{ressource.user_hashid}'
|
||||||
socketio.emit('users.patch', jsonpatch, room=room)
|
socketio.emit('users.patch', jsonpatch, room=room)
|
||||||
|
room = f'/users/{ressource.user_hashid}'
|
||||||
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
@db.event.listens_for(Corpus, 'after_insert')
|
@db.event.listens_for(Corpus, 'after_insert')
|
||||||
@ -1047,8 +1049,8 @@ def ressource_after_insert_handler(mapper, connection, ressource):
|
|||||||
jsonpatch = [
|
jsonpatch = [
|
||||||
{'op': 'add', 'path': ressource.jsonpatch_path, 'value': value}
|
{'op': 'add', 'path': ressource.jsonpatch_path, 'value': value}
|
||||||
]
|
]
|
||||||
room = f'users.{ressource.user_hashid}'
|
room = f'/users/{ressource.user_hashid}'
|
||||||
socketio.emit('users.patch', jsonpatch, room=room)
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
@db.event.listens_for(Corpus, 'after_update')
|
@db.event.listens_for(Corpus, 'after_update')
|
||||||
@ -1077,8 +1079,8 @@ def ressource_after_update_handler(mapper, connection, ressource):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if jsonpatch:
|
if jsonpatch:
|
||||||
room = f'users.{ressource.user_hashid}'
|
room = f'/users/{ressource.user_hashid}'
|
||||||
socketio.emit('users.patch', jsonpatch, room=room)
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
@db.event.listens_for(Job, 'after_update')
|
@db.event.listens_for(Job, 'after_update')
|
||||||
|
@ -1,21 +1,30 @@
|
|||||||
class App {
|
class App {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.data = {users: {}};
|
this.data = {users: {}};
|
||||||
this.eventListeners = {'users.patch': []};
|
|
||||||
this.promises = {users: {}};
|
this.promises = {users: {}};
|
||||||
this.socket = io({transports: ['websocket'], upgrade: false});
|
this.socket = io({transports: ['websocket'], upgrade: false});
|
||||||
this.socket.on('users.patch', patch => this.usersPatchHandler(patch));
|
this.socket.on('PATCH', (patch) => {this.data = jsonpatch.applyPatch(this.data, patch).newDocument;});
|
||||||
}
|
}
|
||||||
|
|
||||||
get users() {
|
get users() {
|
||||||
return this.data.users;
|
return this.data.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(type, listener) {
|
subscribeUser(userId) {
|
||||||
if (!(type in this.eventListeners)) {
|
if (userId in this.promises.users) {
|
||||||
throw `Unknown event type: ${type}`;
|
return this.promises.users[userId];
|
||||||
}
|
}
|
||||||
this.eventListeners[type].push(listener);
|
this.promises.users[userId] = new Promise((resolve, reject) => {
|
||||||
|
this.socket.emit('SUBSCRIBE /users/<user_id>', userId, response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.data.users[userId] = response.payload;
|
||||||
|
resolve(this.data.users[userId]);
|
||||||
|
} else {
|
||||||
|
reject(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return this.promises.users[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
flash(message, category) {
|
flash(message, category) {
|
||||||
@ -50,29 +59,4 @@ class App {
|
|||||||
toastCloseActionElement = toast.el.querySelector('.toast-action[data-action="close"]');
|
toastCloseActionElement = toast.el.querySelector('.toast-action[data-action="close"]');
|
||||||
toastCloseActionElement.addEventListener('click', () => {toast.dismiss();});
|
toastCloseActionElement.addEventListener('click', () => {toast.dismiss();});
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserById(userId) {
|
|
||||||
if (userId in this.promises.users) {
|
|
||||||
return this.promises.users[userId];
|
|
||||||
}
|
|
||||||
this.promises.users[userId] = new Promise((resolve, reject) => {
|
|
||||||
this.socket.emit('users.user.get', userId, response => {
|
|
||||||
if (response.code === 200) {
|
|
||||||
this.data.users[userId] = response.payload;
|
|
||||||
resolve(this.data.users[userId]);
|
|
||||||
} else {
|
|
||||||
reject(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return this.promises.users[userId];
|
|
||||||
}
|
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
|
||||||
let listener;
|
|
||||||
|
|
||||||
this.data = jsonpatch.applyPatch(this.data, patch).newDocument;
|
|
||||||
//this.data = jsonpatch.apply_patch(this.data, patch);
|
|
||||||
for (listener of this.eventListeners['users.patch']) {listener(patch);}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
class JobStatusNotifier {
|
class JobStatusNotifier {
|
||||||
constructor(userId) {
|
constructor(userId) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let jobId;
|
let jobId;
|
||||||
let match;
|
let match;
|
||||||
let operation;
|
let operation;
|
||||||
let re;
|
let re;
|
||||||
|
|
||||||
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/status$`)
|
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/status$`);
|
||||||
filteredPatch = patch
|
filteredPatch = patch
|
||||||
.filter(operation => operation.op === 'replace')
|
.filter(operation => operation.op === 'replace')
|
||||||
.filter(operation => re.test(operation.path));
|
.filter(operation => re.test(operation.path));
|
||||||
|
@ -16,7 +16,7 @@ class CorpusDisplay extends RessourceDisplay {
|
|||||||
this.setNumTokens(corpus.num_tokens);
|
this.setNumTokens(corpus.num_tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let operation;
|
let operation;
|
||||||
let re;
|
let re;
|
||||||
|
@ -18,7 +18,7 @@ class JobDisplay extends RessourceDisplay {
|
|||||||
this.setTitle(job.title);
|
this.setTitle(job.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let operation;
|
let operation;
|
||||||
let re;
|
let re;
|
||||||
|
@ -2,13 +2,13 @@ class RessourceDisplay {
|
|||||||
constructor(displayElement) {
|
constructor(displayElement) {
|
||||||
this.displayElement = displayElement;
|
this.displayElement = displayElement;
|
||||||
this.userId = this.displayElement.dataset.userId;
|
this.userId = this.displayElement.dataset.userId;
|
||||||
app.addEventListener('users.patch', patch => this.usersPatchHandler(patch));
|
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
||||||
app.getUserById(this.userId).then(user => this.init(user));
|
app.subscribeUser(this.userId).then((user) => {this.init(user);});
|
||||||
}
|
}
|
||||||
|
|
||||||
init(user) {throw 'Not implemented';}
|
init(user) {throw 'Not implemented';}
|
||||||
|
|
||||||
usersPatchHandler(patch) {throw 'Not implemented';}
|
onPATCH(patch) {throw 'Not implemented';}
|
||||||
|
|
||||||
setElement(element, value) {
|
setElement(element, value) {
|
||||||
switch (element.tagName) {
|
switch (element.tagName) {
|
||||||
|
@ -96,7 +96,7 @@ class CorpusFileList extends RessourceList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let corpusFileId;
|
let corpusFileId;
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let match;
|
let match;
|
||||||
|
@ -88,7 +88,7 @@ class CorpusList extends RessourceList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let corpusId;
|
let corpusId;
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let match;
|
let match;
|
||||||
|
@ -54,5 +54,5 @@ class JobInputList extends RessourceList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {return;}
|
onPATCH(patch) {return;}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ class JobList extends RessourceList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let jobId;
|
let jobId;
|
||||||
let match;
|
let match;
|
||||||
|
@ -57,7 +57,7 @@ class JobResultList extends RessourceList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let operation;
|
let operation;
|
||||||
let re;
|
let re;
|
||||||
|
@ -89,7 +89,7 @@ class QueryResultList extends RessourceList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usersPatchHandler(patch) {
|
onPATCH(patch) {
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let match;
|
let match;
|
||||||
let operation;
|
let operation;
|
||||||
|
@ -91,10 +91,10 @@ class RessourceList {
|
|||||||
this.userId = this.listjs.listContainer.dataset.userId;
|
this.userId = this.listjs.listContainer.dataset.userId;
|
||||||
this.listjs.list.addEventListener('click', event => this.onclick(event));
|
this.listjs.list.addEventListener('click', event => this.onclick(event));
|
||||||
if (this.userId) {
|
if (this.userId) {
|
||||||
app.addEventListener('users.patch', patch => this.usersPatchHandler(patch));
|
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
||||||
app.getUserById(this.userId).then(
|
app.subscribeUser(this.userId).then(
|
||||||
user => this.init(user),
|
(user) => {this.init(user);},
|
||||||
error => {throw JSON.stringify(error);}
|
(error) => {throw JSON.stringify(error);}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ class RessourceList {
|
|||||||
|
|
||||||
onclick(event) {throw 'Not implemented';}
|
onclick(event) {throw 'Not implemented';}
|
||||||
|
|
||||||
usersPatchHandler(patch) {throw 'Not implemented';}
|
onPATCH(patch) {throw 'Not implemented';}
|
||||||
|
|
||||||
add(ressources) {
|
add(ressources) {
|
||||||
let values = Array.isArray(ressources) ? ressources : [ressources];
|
let values = Array.isArray(ressources) ? ressources : [ressources];
|
||||||
|
@ -32,11 +32,10 @@
|
|||||||
const jobStatusNotifier = new JobStatusNotifier(currentUserId);
|
const jobStatusNotifier = new JobStatusNotifier(currentUserId);
|
||||||
|
|
||||||
// Initialize components for current user
|
// Initialize components for current user
|
||||||
app.addEventListener('users.patch', patch => jobStatusNotifier.usersPatchHandler(patch));
|
app.subscribeUser(currentUserId)
|
||||||
app.getUserById(currentUserId)
|
|
||||||
.then(
|
.then(
|
||||||
user => {return;},
|
(user) => {return;},
|
||||||
error => {throw JSON.stringify(error);}
|
(error) => {throw JSON.stringify(error);}
|
||||||
);
|
);
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
69
app/templates/users/users.html.j2
Normal file
69
app/templates/users/users.html.j2
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{% extends "base.html.j2" %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<h1 id="title">{{ title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<table class="" id="users"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock page_content %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
|
||||||
|
<script>
|
||||||
|
const updateUrl = (prev, query) => {
|
||||||
|
return prev + (prev.indexOf('?') >= 0 ? '&' : '?') + new URLSearchParams(query).toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
new gridjs.Grid({
|
||||||
|
columns: [
|
||||||
|
{ id: 'username', name: 'Username' },
|
||||||
|
{ id: 'email', name: 'Email' },
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
url: '/users/api_users',
|
||||||
|
then: results => results.data,
|
||||||
|
total: results => results.total,
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
enabled: true,
|
||||||
|
server: {
|
||||||
|
url: (prev, search) => {
|
||||||
|
return updateUrl(prev, {search});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
enabled: true,
|
||||||
|
multiColumn: true,
|
||||||
|
server: {
|
||||||
|
url: (prev, columns) => {
|
||||||
|
const columnIds = ['username', 'email'];
|
||||||
|
const sort = columns.map(col => (col.direction === 1 ? '+' : '-') + columnIds[col.index]);
|
||||||
|
return updateUrl(prev, {sort});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
enabled: true,
|
||||||
|
server: {
|
||||||
|
url: (prev, page, limit) => {
|
||||||
|
return updateUrl(prev, {offset: page * limit, limit: limit});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}).render(document.getElementById('users'));
|
||||||
|
</script>
|
||||||
|
{% endblock scripts %}
|
5
app/users/__init__.py
Normal file
5
app/users/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
|
||||||
|
bp = Blueprint('users', __name__)
|
||||||
|
from . import events, routes # noqa
|
32
app/users/events.py
Normal file
32
app/users/events.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from app import hashids, socketio
|
||||||
|
from app.decorators import socketio_login_required
|
||||||
|
from app.models import User
|
||||||
|
from flask_login import current_user
|
||||||
|
from flask_socketio import join_room, leave_room
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('SUBSCRIBE /users/<user_id>')
|
||||||
|
@socketio_login_required
|
||||||
|
def subscribe_user(user_hashid):
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
if user is None:
|
||||||
|
return {'code': 404, 'msg': 'Not found'}
|
||||||
|
if not (user == current_user or current_user.is_administrator):
|
||||||
|
return {'code': 403, 'msg': 'Forbidden'}
|
||||||
|
dict_user = user.to_dict(backrefs=True, relationships=True)
|
||||||
|
join_room(f'/users/{user.hashid}')
|
||||||
|
return {'code': 200, 'msg': 'OK', 'payload': dict_user}
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('UNSUBSCRIBE /users/<user_id>')
|
||||||
|
@socketio_login_required
|
||||||
|
def subscribe_user(user_hashid):
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
if user is None:
|
||||||
|
return {'code': 404, 'msg': 'Not found'}
|
||||||
|
if not (user == current_user or current_user.is_administrator):
|
||||||
|
return {'code': 403, 'msg': 'Forbidden'}
|
||||||
|
leave_room(f'/users/{user.hashid}')
|
||||||
|
return {'code': 200, 'msg': 'OK'}
|
50
app/users/routes.py
Normal file
50
app/users/routes.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from app.models import User
|
||||||
|
from flask import render_template, request, url_for
|
||||||
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
def users():
|
||||||
|
return render_template(
|
||||||
|
'users/users.html.j2',
|
||||||
|
title='Users'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/api_users')
|
||||||
|
def api_users():
|
||||||
|
query = User.query
|
||||||
|
|
||||||
|
# search filter
|
||||||
|
search = request.args.get('search')
|
||||||
|
if search:
|
||||||
|
query = query.filter(User.username.like(f'%{search}%') | User.email.like(f'%{search}%'))
|
||||||
|
total = query.count()
|
||||||
|
|
||||||
|
# sorting
|
||||||
|
sort = request.args.get('sort')
|
||||||
|
if sort:
|
||||||
|
order = []
|
||||||
|
for s in sort.split(','):
|
||||||
|
direction = s[0]
|
||||||
|
name = s[1:]
|
||||||
|
if name not in ['username', 'email']:
|
||||||
|
name = 'username'
|
||||||
|
col = getattr(User, name)
|
||||||
|
if direction == '-':
|
||||||
|
col = col.desc()
|
||||||
|
order.append(col)
|
||||||
|
if order:
|
||||||
|
query = query.order_by(*order)
|
||||||
|
|
||||||
|
# pagination
|
||||||
|
offset = request.args.get('offset', type=int, default=-1)
|
||||||
|
limit = request.args.get('limit', type=int, default=-1)
|
||||||
|
if offset != -1 and limit != -1:
|
||||||
|
query = query.offset(offset).limit(limit)
|
||||||
|
|
||||||
|
# response
|
||||||
|
return {
|
||||||
|
'data': [user.to_dict() for user in query],
|
||||||
|
'total': total
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user