mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-04 04:12:45 +00:00 
			
		
		
		
	Change the Subscription Logic for Socket.IO Data exchange
This commit is contained in:
		@@ -11,8 +11,6 @@ from flask import (
 | 
			
		||||
    send_from_directory
 | 
			
		||||
)
 | 
			
		||||
from flask_login import current_user, login_required
 | 
			
		||||
from werkzeug.utils import secure_filename
 | 
			
		||||
from zipfile import ZipFile
 | 
			
		||||
from . import bp
 | 
			
		||||
from . import tasks
 | 
			
		||||
from .forms import (
 | 
			
		||||
@@ -24,7 +22,6 @@ from .forms import (
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import tempfile
 | 
			
		||||
import glob
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,25 @@
 | 
			
		||||
class App {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.data = {users: {}};
 | 
			
		||||
    this.promises = {users: {}};
 | 
			
		||||
    this.data = {
 | 
			
		||||
      promises: {getUser: {}, subscribeUser: {}},
 | 
			
		||||
      users: {},
 | 
			
		||||
    };
 | 
			
		||||
    this.socket = io({transports: ['websocket'], upgrade: false});
 | 
			
		||||
    this.socket.on('PATCH', (patch) => {this.data = jsonpatch.applyPatch(this.data, patch).newDocument;});
 | 
			
		||||
    this.socket.on('PATCH', (patch) => {
 | 
			
		||||
      const re = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`);
 | 
			
		||||
      const filteredPatch = patch.filter(operation => re.test(operation.path));
 | 
			
		||||
  
 | 
			
		||||
      jsonpatch.applyPatch(this.data, filteredPatch);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get users() {
 | 
			
		||||
    return this.data.users;
 | 
			
		||||
  getUser(userId) {
 | 
			
		||||
    if (userId in this.data.promises.getUser) {
 | 
			
		||||
      return this.data.promises.getUser[userId];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  subscribeUser(userId) {
 | 
			
		||||
    if (userId in this.promises.users) {
 | 
			
		||||
      return this.promises.users[userId];
 | 
			
		||||
    }
 | 
			
		||||
    this.promises.users[userId] = new Promise((resolve, reject) => {
 | 
			
		||||
      this.socket.emit('SUBSCRIBE /users/<user_id>', userId, response => {
 | 
			
		||||
    this.data.promises.getUser[userId] = new Promise((resolve, reject) => {
 | 
			
		||||
      this.socket.emit('GET /users/<user_id>', userId, (response) => {
 | 
			
		||||
        if (response.code === 200) {
 | 
			
		||||
          this.data.users[userId] = response.payload;
 | 
			
		||||
          resolve(this.data.users[userId]);
 | 
			
		||||
@@ -24,7 +28,26 @@ class App {
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    return this.promises.users[userId];
 | 
			
		||||
 | 
			
		||||
    return this.data.promises.getUser[userId];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  subscribeUser(userId) {
 | 
			
		||||
    if (userId in this.data.promises.subscribeUser) {
 | 
			
		||||
      return this.data.promises.subscribeUser[userId];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.data.promises.subscribeUser[userId] = new Promise((resolve, reject) => {
 | 
			
		||||
      this.socket.emit('SUBSCRIBE /users/<user_id>', userId, (response) => {
 | 
			
		||||
        if (response.code === 200) {
 | 
			
		||||
          resolve(response);
 | 
			
		||||
        } else {
 | 
			
		||||
          reject(response);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return this.data.promises.subscribeUser[userId];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  flash(message, category) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,18 @@
 | 
			
		||||
class JobStatusNotifier {
 | 
			
		||||
  constructor(userId) {
 | 
			
		||||
    this.userId = userId;
 | 
			
		||||
    this.isInitialized = false;
 | 
			
		||||
    app.subscribeUser(this.userId).then((response) => {
 | 
			
		||||
      app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
 | 
			
		||||
    });
 | 
			
		||||
    app.getUser(this.userId).then((user) => {
 | 
			
		||||
      this.isInitialized = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    if (!this.isInitialized) {return;}
 | 
			
		||||
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let jobId;
 | 
			
		||||
    let match;
 | 
			
		||||
@@ -13,11 +21,11 @@ class JobStatusNotifier {
 | 
			
		||||
 | 
			
		||||
    re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/status$`);
 | 
			
		||||
    filteredPatch = patch
 | 
			
		||||
      .filter(operation => operation.op === 'replace')
 | 
			
		||||
      .filter(operation => re.test(operation.path));
 | 
			
		||||
      .filter((operation) => {return operation.op === 'replace';})
 | 
			
		||||
      .filter((operation) => {return re.test(operation.path);});
 | 
			
		||||
    for (operation of filteredPatch) {
 | 
			
		||||
      [match, jobId] = operation.path.match(re);
 | 
			
		||||
      app.flash(`[<a href="/jobs/${jobId}">${app.users[this.userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
 | 
			
		||||
      app.flash(`[<a href="/jobs/${jobId}">${app.data.users[this.userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,8 @@ class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(user) {
 | 
			
		||||
    let corpus;
 | 
			
		||||
    const corpus = user.corpora[this.corpusId];
 | 
			
		||||
 | 
			
		||||
    corpus = user.corpora[this.corpusId];
 | 
			
		||||
    this.setCreationDate(corpus.creation_date);
 | 
			
		||||
    this.setDescription(corpus.description);
 | 
			
		||||
    this.setLastEditedDate(corpus.last_edited_date);
 | 
			
		||||
@@ -17,12 +16,15 @@ class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    if (!this.isInitialized) {return;}
 | 
			
		||||
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let operation;
 | 
			
		||||
    let re;
 | 
			
		||||
 | 
			
		||||
    re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}`);
 | 
			
		||||
    filteredPatch = patch.filter(operation => re.test(operation.path));
 | 
			
		||||
 | 
			
		||||
    for (operation of filteredPatch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'replace':
 | 
			
		||||
@@ -55,7 +57,7 @@ class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
  setNumTokens(numTokens) {
 | 
			
		||||
    this.setElements(
 | 
			
		||||
      this.displayElement.querySelectorAll('.corpus-token-ratio'),
 | 
			
		||||
      `${numTokens}/${app.users[this.userId].corpora[this.corpusId].max_num_tokens}`
 | 
			
		||||
      `${numTokens}/${app.data.users[this.userId].corpora[this.corpusId].max_num_tokens}`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +79,7 @@ class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
    }
 | 
			
		||||
    elements = this.displayElement.querySelectorAll('.corpus-build-trigger');
 | 
			
		||||
    for (element of elements) {
 | 
			
		||||
      if (['UNPREPARED', 'FAILED'].includes(status) && Object.values(app.users[this.userId].corpora[this.corpusId].files).length > 0) {
 | 
			
		||||
      if (['UNPREPARED', 'FAILED'].includes(status) && Object.values(app.data.users[this.userId].corpora[this.corpusId].files).length > 0) {
 | 
			
		||||
        element.classList.remove('disabled');
 | 
			
		||||
      } else {
 | 
			
		||||
        element.classList.add('disabled');
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,8 @@ class JobDisplay extends RessourceDisplay {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(user) {
 | 
			
		||||
    let job;
 | 
			
		||||
    const job = user.jobs[this.jobId];
 | 
			
		||||
 | 
			
		||||
    job = user.jobs[this.jobId];
 | 
			
		||||
    this.setCreationDate(job.creation_date);
 | 
			
		||||
    this.setEndDate(job.creation_date);
 | 
			
		||||
    this.setDescription(job.description);
 | 
			
		||||
@@ -19,12 +18,15 @@ class JobDisplay extends RessourceDisplay {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    if (!this.isInitialized) {return;}
 | 
			
		||||
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let operation;
 | 
			
		||||
    let re;
 | 
			
		||||
 | 
			
		||||
    re = new RegExp(`^/users/${this.userId}/jobs/${this.jobId}`);
 | 
			
		||||
    filteredPatch = patch.filter(operation => re.test(operation.path));
 | 
			
		||||
 | 
			
		||||
    for (operation of filteredPatch) {
 | 
			
		||||
      switch(operation.op) {
 | 
			
		||||
        case 'replace':
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,16 @@ class RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    this.displayElement = displayElement;
 | 
			
		||||
    this.userId = this.displayElement.dataset.userId;
 | 
			
		||||
    this.isInitialized = false;
 | 
			
		||||
    if (this.userId) {
 | 
			
		||||
      app.subscribeUser(this.userId).then((response) => {
 | 
			
		||||
        app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
 | 
			
		||||
    app.subscribeUser(this.userId).then((user) => {this.init(user);});
 | 
			
		||||
      });
 | 
			
		||||
      app.getUser(this.userId).then((user) => {
 | 
			
		||||
        this.init(user);
 | 
			
		||||
        this.isInitialized = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(user) {throw 'Not implemented';}
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ class CorpusFileList extends RessourceList {
 | 
			
		||||
          <div class="modal">
 | 
			
		||||
            <div class="modal-content">
 | 
			
		||||
              <h4>Confirm corpus deletion</h4>
 | 
			
		||||
              <p>Do you really want to delete the corpus file <b>${app.users[this.userId].corpora[this.corpusId].files[corpusFileId].filename}</b>? It will be permanently deleted!</p>
 | 
			
		||||
              <p>Do you really want to delete the corpus file <b>${app.data.users[this.userId].corpora[this.corpusId].files[corpusFileId].filename}</b>? It will be permanently deleted!</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
              <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
@@ -97,6 +97,8 @@ class CorpusFileList extends RessourceList {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    if (!this.isInitialized) {return;}
 | 
			
		||||
 | 
			
		||||
    let corpusFileId;
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let match;
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ class CorpusList extends RessourceList {
 | 
			
		||||
          <div class="modal">
 | 
			
		||||
            <div class="modal-content">
 | 
			
		||||
              <h4>Confirm corpus deletion</h4>
 | 
			
		||||
              <p>Do you really want to delete the corpus <b>${app.users[this.userId].corpora[corpusId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
              <p>Do you really want to delete the corpus <b>${app.data.users[this.userId].corpora[corpusId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
              <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
@@ -89,6 +89,8 @@ class CorpusList extends RessourceList {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    if (!this.isInitialized) {return;}
 | 
			
		||||
 | 
			
		||||
    let corpusId;
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let match;
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,6 @@ class JobList extends RessourceList {
 | 
			
		||||
    ]
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  constructor(listElement, options = {}) {
 | 
			
		||||
    super(listElement, {...JobList.options, ...options});
 | 
			
		||||
  }
 | 
			
		||||
@@ -66,7 +65,7 @@ class JobList extends RessourceList {
 | 
			
		||||
          <div class="modal">
 | 
			
		||||
            <div class="modal-content">
 | 
			
		||||
              <h4>Confirm job deletion</h4>
 | 
			
		||||
              <p>Do you really want to delete the job <b>${app.users[this.userId].jobs[jobId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
              <p>Do you really want to delete the job <b>${app.data.users[this.userId].jobs[jobId].title}</b>? All files will be permanently deleted!</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
              <a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
 | 
			
		||||
@@ -95,6 +94,8 @@ class JobList extends RessourceList {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    if (!this.isInitialized) {return;}
 | 
			
		||||
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let jobId;
 | 
			
		||||
    let match;
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,8 @@ class JobResultList extends RessourceList {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    if (!this.isInitialized) {return;}
 | 
			
		||||
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let operation;
 | 
			
		||||
    let re;
 | 
			
		||||
 
 | 
			
		||||
@@ -90,12 +90,15 @@ class RessourceList {
 | 
			
		||||
    this.listjs.list.style.cursor = 'pointer';
 | 
			
		||||
    this.userId = this.listjs.listContainer.dataset.userId;
 | 
			
		||||
    this.listjs.list.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
    this.isInitialized = false;
 | 
			
		||||
    if (this.userId) {
 | 
			
		||||
      app.subscribeUser(this.userId).then((response) => {
 | 
			
		||||
        app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
 | 
			
		||||
      app.subscribeUser(this.userId).then(
 | 
			
		||||
        (user) => {this.init(user);},
 | 
			
		||||
        (error) => {throw JSON.stringify(error);}
 | 
			
		||||
      );
 | 
			
		||||
      });
 | 
			
		||||
      app.getUser(this.userId).then((user) => {
 | 
			
		||||
        this.init(user);
 | 
			
		||||
        this.isInitialized = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ class UserList extends RessourceList {
 | 
			
		||||
        'id-1': user.id,
 | 
			
		||||
        'username': user.username,
 | 
			
		||||
        'email': user.email,
 | 
			
		||||
        'last-seen': new Date(user.last_seen).toLocaleString("en-US"),
 | 
			
		||||
        'last-seen': new Date(user.last_seen).toLocaleString('en-US'),
 | 
			
		||||
        'member-since': user.member_since,
 | 
			
		||||
        'role': user.role.name
 | 
			
		||||
      };
 | 
			
		||||
 
 | 
			
		||||
@@ -32,11 +32,7 @@
 | 
			
		||||
  const jobStatusNotifier = new JobStatusNotifier(currentUserId);
 | 
			
		||||
 | 
			
		||||
  // Initialize components for current user
 | 
			
		||||
  app.subscribeUser(currentUserId)
 | 
			
		||||
    .then(
 | 
			
		||||
      (user) => {return;},
 | 
			
		||||
      (error) => {throw JSON.stringify(error);}
 | 
			
		||||
    );
 | 
			
		||||
  app.subscribeUser(currentUserId).catch((error) => {throw JSON.stringify(error);});
 | 
			
		||||
  {%- endif %}
 | 
			
		||||
 | 
			
		||||
  // Disable all option elements with no value
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
{% set breadcrumbs %}
 | 
			
		||||
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
 | 
			
		||||
<li class="tab"><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" target="_self">My jobs</a></li>
 | 
			
		||||
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
 | 
			
		||||
{% if request.path == url_for('.job', job_id=job.id) %}
 | 
			
		||||
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
 | 
			
		||||
<li class="tab"><a class="active" href="{{ url_for('.job', job_id=job.id) }}" target="_self">{{ job.title }}</a></li>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endset %}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,28 @@
 | 
			
		||||
      <h1 id="title">{{ title }}</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="col s12">
 | 
			
		||||
    <div class="col s12 nopaque-ressource-list no-autoinit" data-ressource-type="User" id="users">
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <div class="card-content">
 | 
			
		||||
          <table class="" id="users"></table>
 | 
			
		||||
          <div class="input-field">
 | 
			
		||||
            <i class="material-icons prefix">search</i>
 | 
			
		||||
            <input id="search-user" class="search" type="text"></input>
 | 
			
		||||
            <label for="search-user">Search user</label>
 | 
			
		||||
          </div>
 | 
			
		||||
          <table>
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th>Id</th>
 | 
			
		||||
                <th>Username</th>
 | 
			
		||||
                <th>Email</th>
 | 
			
		||||
                <th>Last seen</th>
 | 
			
		||||
                <th>Role</th>
 | 
			
		||||
                <th></th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody class="list"></tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
          <ul class="pagination"></ul>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -18,52 +36,10 @@
 | 
			
		||||
</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'));
 | 
			
		||||
  let userList = new UserList(document.querySelector('#users'));
 | 
			
		||||
  userList.init({{ dict_users|tojson }});
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock scripts %}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,19 @@ from flask_login import current_user
 | 
			
		||||
from flask_socketio import join_room, leave_room
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('GET /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)
 | 
			
		||||
    return {'code': 200, 'msg': 'OK', 'payload': dict_user}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('SUBSCRIBE /users/<user_id>')
 | 
			
		||||
@socketio_login_required
 | 
			
		||||
def subscribe_user(user_hashid):
 | 
			
		||||
@@ -14,9 +27,10 @@ def subscribe_user(user_hashid):
 | 
			
		||||
        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)
 | 
			
		||||
    # dict_user = user.to_dict(backrefs=True, relationships=True)
 | 
			
		||||
    join_room(f'/users/{user.hashid}')
 | 
			
		||||
    return {'code': 200, 'msg': 'OK', 'payload': dict_user}
 | 
			
		||||
    # return {'code': 200, 'msg': 'OK', 'payload': dict_user}
 | 
			
		||||
    return {'code': 200, 'msg': 'OK'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('UNSUBSCRIBE /users/<user_id>')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +1,17 @@
 | 
			
		||||
from app.decorators import admin_required
 | 
			
		||||
from app.models import User
 | 
			
		||||
from flask import render_template, request, url_for
 | 
			
		||||
from flask import render_template, request
 | 
			
		||||
from flask_login import login_required
 | 
			
		||||
from . import bp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/')
 | 
			
		||||
@login_required
 | 
			
		||||
@admin_required
 | 
			
		||||
def users():
 | 
			
		||||
    dict_users = [u.to_dict(backrefs=True, relationships=False) for u in User.query.all()]
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'users/users.html.j2',
 | 
			
		||||
        title='Users'
 | 
			
		||||
        title='Users',
 | 
			
		||||
        dict_users=dict_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
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user