mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-26 03:14:19 +00:00
Update settings
This commit is contained in:
parent
9a805b9d14
commit
cda28910f5
@ -135,9 +135,6 @@ def create_app(config: Config = Config) -> Flask:
|
|||||||
|
|
||||||
from .namespaces.corpora import CorporaNamespace
|
from .namespaces.corpora import CorporaNamespace
|
||||||
socketio.on_namespace(CorporaNamespace('/corpora'))
|
socketio.on_namespace(CorporaNamespace('/corpora'))
|
||||||
|
|
||||||
from .namespaces.users import UsersNamespace
|
|
||||||
socketio.on_namespace(UsersNamespace('/users'))
|
|
||||||
# endregion SocketIO Namespaces
|
# endregion SocketIO Namespaces
|
||||||
|
|
||||||
# region Database event Listeners
|
# region Database event Listeners
|
||||||
|
@ -1,18 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask_login import login_required
|
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('settings', __name__)
|
bp = Blueprint('settings', __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.before_request
|
|
||||||
@login_required
|
|
||||||
def before_request():
|
|
||||||
'''
|
|
||||||
Ensures that the routes in this package can only be visited by users that
|
|
||||||
are logged in.
|
|
||||||
'''
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
from . import routes
|
from . import routes
|
||||||
|
@ -39,7 +39,7 @@ class UpdateAccountInformationForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField()
|
submit = SubmitField()
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user: User, *args, **kwargs):
|
||||||
if 'data' not in kwargs:
|
if 'data' not in kwargs:
|
||||||
kwargs['data'] = user.to_json_serializeable()
|
kwargs['data'] = user.to_json_serializeable()
|
||||||
if 'prefix' not in kwargs:
|
if 'prefix' not in kwargs:
|
||||||
@ -89,7 +89,7 @@ class UpdateProfileInformationForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField()
|
submit = SubmitField()
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user: User, *args, **kwargs):
|
||||||
if 'data' not in kwargs:
|
if 'data' not in kwargs:
|
||||||
kwargs['data'] = user.to_json_serializeable()
|
kwargs['data'] = user.to_json_serializeable()
|
||||||
if 'prefix' not in kwargs:
|
if 'prefix' not in kwargs:
|
||||||
@ -130,7 +130,7 @@ class UpdatePasswordForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField()
|
submit = SubmitField()
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user: User, *args, **kwargs):
|
||||||
if 'prefix' not in kwargs:
|
if 'prefix' not in kwargs:
|
||||||
kwargs['prefix'] = 'update-password-form'
|
kwargs['prefix'] = 'update-password-form'
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -152,7 +152,7 @@ class UpdateNotificationsForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField()
|
submit = SubmitField()
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user: User, *args, **kwargs):
|
||||||
if 'data' not in kwargs:
|
if 'data' not in kwargs:
|
||||||
kwargs['data'] = user.to_json_serializeable()
|
kwargs['data'] = user.to_json_serializeable()
|
||||||
if 'prefix' not in kwargs:
|
if 'prefix' not in kwargs:
|
@ -1,10 +1,158 @@
|
|||||||
from flask import g, url_for
|
from flask import (
|
||||||
from flask_login import current_user
|
abort,
|
||||||
from app.blueprints.users.settings.routes import settings as settings_route
|
flash,
|
||||||
|
jsonify,
|
||||||
|
redirect,
|
||||||
|
render_template,
|
||||||
|
request,
|
||||||
|
url_for
|
||||||
|
)
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from app import db
|
||||||
|
from app.models import Avatar
|
||||||
from . import bp
|
from . import bp
|
||||||
|
from .forms import (
|
||||||
|
UpdateAvatarForm,
|
||||||
|
UpdatePasswordForm,
|
||||||
|
UpdateNotificationsForm,
|
||||||
|
UpdateAccountInformationForm,
|
||||||
|
UpdateProfileInformationForm
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/settings', methods=['GET', 'POST'])
|
@bp.route('', methods=['GET', 'POST'])
|
||||||
def settings():
|
@login_required
|
||||||
g._nopaque_redirect_location_on_post = url_for('.settings')
|
def index():
|
||||||
return settings_route(current_user.id)
|
update_account_information_form = UpdateAccountInformationForm(current_user)
|
||||||
|
update_profile_information_form = UpdateProfileInformationForm(current_user)
|
||||||
|
update_avatar_form = UpdateAvatarForm()
|
||||||
|
update_password_form = UpdatePasswordForm(current_user)
|
||||||
|
update_notifications_form = UpdateNotificationsForm(current_user)
|
||||||
|
|
||||||
|
# region handle update profile information form
|
||||||
|
if update_profile_information_form.submit.data and update_profile_information_form.validate():
|
||||||
|
current_user.about_me = update_profile_information_form.about_me.data
|
||||||
|
current_user.location = update_profile_information_form.location.data
|
||||||
|
current_user.organization = update_profile_information_form.organization.data
|
||||||
|
current_user.website = update_profile_information_form.website.data
|
||||||
|
current_user.full_name = update_profile_information_form.full_name.data
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
# endregion handle update profile information form
|
||||||
|
|
||||||
|
# region handle update avatar form
|
||||||
|
if update_avatar_form.submit.data and update_avatar_form.validate():
|
||||||
|
try:
|
||||||
|
Avatar.create(
|
||||||
|
update_avatar_form.avatar.data,
|
||||||
|
user=current_user
|
||||||
|
)
|
||||||
|
except (AttributeError, OSError):
|
||||||
|
abort(500)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
# endregion handle update avatar form
|
||||||
|
|
||||||
|
# region handle update account information form
|
||||||
|
if update_account_information_form.submit.data and update_account_information_form.validate():
|
||||||
|
current_user.email = update_account_information_form.email.data
|
||||||
|
current_user.username = update_account_information_form.username.data
|
||||||
|
db.session.commit()
|
||||||
|
flash('Profile settings updated')
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
# endregion handle update account information form
|
||||||
|
|
||||||
|
# region handle update password form
|
||||||
|
if update_password_form.submit.data and update_password_form.validate():
|
||||||
|
current_user.password = update_password_form.new_password.data
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
# endregion handle update password form
|
||||||
|
|
||||||
|
# region handle update notifications form
|
||||||
|
if update_notifications_form.submit.data and update_notifications_form.validate():
|
||||||
|
current_user.setting_job_status_mail_notification_level = \
|
||||||
|
update_notifications_form.job_status_mail_notification_level.data
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
# endregion handle update notifications form
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'settings/index.html.j2',
|
||||||
|
title='Settings',
|
||||||
|
update_account_information_form=update_account_information_form,
|
||||||
|
update_avatar_form=update_avatar_form,
|
||||||
|
update_notifications_form=update_notifications_form,
|
||||||
|
update_password_form=update_password_form,
|
||||||
|
update_profile_information_form=update_profile_information_form,
|
||||||
|
user=current_user
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/profile-is-public', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_profile_is_public():
|
||||||
|
new_value = request.json
|
||||||
|
|
||||||
|
if not isinstance(new_value, bool):
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
current_user.is_public = new_value
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify('Your changes have been saved'), 200
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/profile-show-email', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_profile_show_email():
|
||||||
|
new_value = request.json
|
||||||
|
|
||||||
|
if not isinstance(new_value, bool):
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
if new_value:
|
||||||
|
current_user.add_profile_privacy_setting('SHOW_EMAIL')
|
||||||
|
else:
|
||||||
|
current_user.remove_profile_privacy_setting('SHOW_EMAIL')
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify('Your changes have been saved'), 200
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/profile-show-last-seen', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_profile_show_last_seen():
|
||||||
|
new_value = request.json
|
||||||
|
|
||||||
|
if not isinstance(new_value, bool):
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
if new_value:
|
||||||
|
current_user.add_profile_privacy_setting('SHOW_LAST_SEEN')
|
||||||
|
else:
|
||||||
|
current_user.remove_profile_privacy_setting('SHOW_LAST_SEEN')
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify('Your changes have been saved'), 200
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/profile-show-member-since', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_profile_show_member_since():
|
||||||
|
new_value = request.json
|
||||||
|
|
||||||
|
if not isinstance(new_value, bool):
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
if new_value:
|
||||||
|
current_user.add_profile_privacy_setting('SHOW_MEMBER_SINCE')
|
||||||
|
else:
|
||||||
|
current_user.remove_profile_privacy_setting('SHOW_MEMBER_SINCE')
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify('Your changes have been saved'), 200
|
||||||
|
@ -1,18 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask_login import login_required
|
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('users', __name__)
|
bp = Blueprint('users', __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.before_request
|
from . import cli, events, routes
|
||||||
@login_required
|
|
||||||
def before_request():
|
|
||||||
'''
|
|
||||||
Ensures that the routes in this package can only be visited by users that
|
|
||||||
are logged in.
|
|
||||||
'''
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
from . import cli, json_routes, routes, settings
|
|
||||||
|
58
app/blueprints/users/events.py
Normal file
58
app/blueprints/users/events.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from flask_login import current_user
|
||||||
|
from flask_socketio import join_room, leave_room
|
||||||
|
from app import hashids, socketio
|
||||||
|
from app.decorators import socketio_login_required
|
||||||
|
from app.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('SUBSCRIBE User')
|
||||||
|
@socketio_login_required
|
||||||
|
def subscribe(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):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
join_room(f'/users/{user.hashid}')
|
||||||
|
|
||||||
|
return {'status': 200, 'statusText': 'OK'}
|
||||||
|
|
||||||
|
@socketio.on('UNSUBSCRIBE User')
|
||||||
|
@socketio_login_required
|
||||||
|
def unsubscribe(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):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
leave_room(f'/users/{user.hashid}')
|
||||||
|
|
||||||
|
return {'status': 200, 'statusText': 'OK'}
|
@ -1,69 +0,0 @@
|
|||||||
from flask import abort, current_app
|
|
||||||
from flask_login import current_user, logout_user
|
|
||||||
from threading import Thread
|
|
||||||
from app import db
|
|
||||||
from app.decorators import content_negotiation
|
|
||||||
from app.models import Avatar, User
|
|
||||||
from . import bp
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:user_id>', methods=['DELETE'])
|
|
||||||
@content_negotiation(produces='application/json')
|
|
||||||
def delete_user(user_id):
|
|
||||||
def _delete_user(app, user_id):
|
|
||||||
with app.app_context():
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
user.delete()
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
user = User.query.get_or_404(user_id)
|
|
||||||
if not (user == current_user or current_user.is_administrator):
|
|
||||||
abort(403)
|
|
||||||
thread = Thread(
|
|
||||||
target=_delete_user,
|
|
||||||
args=(current_app._get_current_object(), user.id)
|
|
||||||
)
|
|
||||||
if user == current_user:
|
|
||||||
logout_user()
|
|
||||||
thread.start()
|
|
||||||
response_data = {
|
|
||||||
'message': f'User "{user.username}" marked for deletion'
|
|
||||||
}
|
|
||||||
return response_data, 202
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:user_id>/avatar', methods=['DELETE'])
|
|
||||||
@content_negotiation(produces='application/json')
|
|
||||||
def delete_user_avatar(user_id):
|
|
||||||
def _delete_avatar(app, avatar_id):
|
|
||||||
with app.app_context():
|
|
||||||
avatar = Avatar.query.get(avatar_id)
|
|
||||||
avatar.delete()
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
user = User.query.get_or_404(user_id)
|
|
||||||
if user.avatar is None:
|
|
||||||
abort(404)
|
|
||||||
if not (user == current_user or current_user.is_administrator):
|
|
||||||
abort(403)
|
|
||||||
thread = Thread(
|
|
||||||
target=_delete_avatar,
|
|
||||||
args=(current_app._get_current_object(), user.avatar.id)
|
|
||||||
)
|
|
||||||
thread.start()
|
|
||||||
response_data = {
|
|
||||||
'message': f'Avatar marked for deletion'
|
|
||||||
}
|
|
||||||
return response_data, 202
|
|
||||||
|
|
||||||
@bp.route('/accept-terms-of-use', methods=['POST'])
|
|
||||||
@content_negotiation(produces='application/json')
|
|
||||||
def accept_terms_of_use():
|
|
||||||
if not (current_user.is_authenticated or current_user.confirmed):
|
|
||||||
abort(403)
|
|
||||||
current_user.terms_of_use_accepted = True
|
|
||||||
db.session.commit()
|
|
||||||
response_data = {
|
|
||||||
'message': 'You accepted the terms of use',
|
|
||||||
}
|
|
||||||
return response_data, 202
|
|
@ -1,25 +1,48 @@
|
|||||||
from flask import (
|
from flask import (
|
||||||
abort,
|
abort,
|
||||||
|
current_app,
|
||||||
|
Flask,
|
||||||
|
jsonify,
|
||||||
redirect,
|
redirect,
|
||||||
render_template,
|
render_template,
|
||||||
|
request,
|
||||||
send_from_directory,
|
send_from_directory,
|
||||||
url_for
|
url_for
|
||||||
)
|
)
|
||||||
from flask_login import current_user
|
from flask_login import current_user, login_required, logout_user
|
||||||
from app.models import User
|
from threading import Thread
|
||||||
|
from app import db
|
||||||
|
from app.models import Avatar, User
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@bp.route('')
|
@bp.route('')
|
||||||
def users():
|
@login_required
|
||||||
|
def index():
|
||||||
return redirect(url_for('main.social_area', _anchor='users'))
|
return redirect(url_for('main.social_area', _anchor='users'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:user_id>')
|
@bp.route('/<hashid:user_id>')
|
||||||
def user(user_id):
|
@login_required
|
||||||
|
def user(user_id: int):
|
||||||
user = User.query.get_or_404(user_id)
|
user = User.query.get_or_404(user_id)
|
||||||
if not (user.is_public or user == current_user or current_user.is_administrator):
|
|
||||||
|
if not (
|
||||||
|
user.is_public
|
||||||
|
or user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
accept_json = request.accept_mimetypes.accept_json
|
||||||
|
accept_html = request.accept_mimetypes.accept_html
|
||||||
|
|
||||||
|
if accept_json and not accept_html:
|
||||||
|
return user.to_json_serializeable(
|
||||||
|
backrefs=True,
|
||||||
|
relationships=True
|
||||||
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'users/user.html.j2',
|
'users/user.html.j2',
|
||||||
title=user.username,
|
title=user.username,
|
||||||
@ -27,13 +50,51 @@ def user(user_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:user_id>/avatar')
|
def _delete_user(app: Flask, user_id: int):
|
||||||
def user_avatar(user_id):
|
with app.app_context():
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
user.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<hashid:user_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_user(user_id: int):
|
||||||
user = User.query.get_or_404(user_id)
|
user = User.query.get_or_404(user_id)
|
||||||
if not (user.is_public or user == current_user or current_user.is_administrator):
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
if user == current_user:
|
||||||
|
logout_user()
|
||||||
|
|
||||||
|
thread = Thread(
|
||||||
|
target=_delete_user,
|
||||||
|
args=(current_app._get_current_object(), user.id)
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
return jsonify(f'User "{user.username}" marked for deletion'), 202
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<hashid:user_id>/avatar')
|
||||||
|
@login_required
|
||||||
|
def user_avatar(user_id: int):
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user.is_public
|
||||||
|
or user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
abort(403)
|
||||||
|
|
||||||
if user.avatar is None:
|
if user.avatar is None:
|
||||||
return redirect(url_for('static', filename='images/user_avatar.png'))
|
return redirect(url_for('static', filename='images/user_avatar.png'))
|
||||||
|
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
user.avatar.path.parent,
|
user.avatar.path.parent,
|
||||||
user.avatar.path.name,
|
user.avatar.path.name,
|
||||||
@ -41,3 +102,49 @@ def user_avatar(user_id):
|
|||||||
download_name=user.avatar.filename,
|
download_name=user.avatar.filename,
|
||||||
mimetype=user.avatar.mimetype
|
mimetype=user.avatar.mimetype
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_avatar(app: Flask, avatar_id: int):
|
||||||
|
with app.app_context():
|
||||||
|
avatar = Avatar.query.get(avatar_id)
|
||||||
|
avatar.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<hashid:user_id>/avatar', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_user_avatar(user_id: int):
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
|
||||||
|
if user.avatar is None:
|
||||||
|
abort(409)
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
thread = Thread(
|
||||||
|
target=_delete_avatar,
|
||||||
|
args=(current_app._get_current_object(), user.avatar.id)
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
return jsonify('Avatar marked for deletion'), 202
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Move this to main blueprint(?)
|
||||||
|
@bp.route('/accept-terms-of-use', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def accept_terms_of_use():
|
||||||
|
if not (
|
||||||
|
current_user.is_authenticated
|
||||||
|
or current_user.confirmed
|
||||||
|
):
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
current_user.terms_of_use_accepted = True
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify('You accepted the terms of use'), 202
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
from .. import bp
|
|
||||||
from . import json_routes, routes
|
|
@ -1,49 +0,0 @@
|
|||||||
from flask import abort, request
|
|
||||||
from flask_login import current_user
|
|
||||||
from app import db
|
|
||||||
from app.decorators import content_negotiation
|
|
||||||
from app.models import User, ProfilePrivacySettings
|
|
||||||
from . import bp
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:user_id>/settings/profile-privacy/is-public', methods=['PUT'])
|
|
||||||
@content_negotiation(consumes='application/json', produces='application/json')
|
|
||||||
def update_user_profile_privacy_setting_is_public(user_id):
|
|
||||||
user = User.query.get_or_404(user_id)
|
|
||||||
if not (user == current_user or current_user.is_administrator):
|
|
||||||
abort(403)
|
|
||||||
enabled = request.json
|
|
||||||
if not isinstance(enabled, bool):
|
|
||||||
abort(400)
|
|
||||||
user.is_public = enabled
|
|
||||||
db.session.commit()
|
|
||||||
response_data = {
|
|
||||||
'message': 'Profile privacy settings updated',
|
|
||||||
'category': 'settings'
|
|
||||||
}
|
|
||||||
return response_data, 200
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:user_id>/settings/profile-privacy/<string:profile_privacy_setting_name>', methods=['PUT'])
|
|
||||||
@content_negotiation(consumes='application/json', produces='application/json')
|
|
||||||
def update_user_profile_privacy_settings(user_id, profile_privacy_setting_name):
|
|
||||||
user = User.query.get_or_404(user_id)
|
|
||||||
try:
|
|
||||||
profile_privacy_setting = ProfilePrivacySettings[profile_privacy_setting_name]
|
|
||||||
except KeyError:
|
|
||||||
abort(404)
|
|
||||||
if not (user == current_user or current_user.is_administrator):
|
|
||||||
abort(403)
|
|
||||||
enabled = request.json
|
|
||||||
if not isinstance(enabled, bool):
|
|
||||||
abort(400)
|
|
||||||
if enabled:
|
|
||||||
user.add_profile_privacy_setting(profile_privacy_setting)
|
|
||||||
else:
|
|
||||||
user.remove_profile_privacy_setting(profile_privacy_setting)
|
|
||||||
db.session.commit()
|
|
||||||
response_data = {
|
|
||||||
'message': 'Profile privacy settings updated',
|
|
||||||
'category': 'settings'
|
|
||||||
}
|
|
||||||
return response_data, 200
|
|
@ -1,93 +0,0 @@
|
|||||||
from flask import abort, flash, g, redirect, render_template, url_for
|
|
||||||
from flask_login import current_user
|
|
||||||
from app import db
|
|
||||||
from app.models import Avatar, User
|
|
||||||
from . import bp
|
|
||||||
from .forms import (
|
|
||||||
UpdateAvatarForm,
|
|
||||||
UpdatePasswordForm,
|
|
||||||
UpdateNotificationsForm,
|
|
||||||
UpdateAccountInformationForm,
|
|
||||||
UpdateProfileInformationForm
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:user_id>/settings', methods=['GET', 'POST'])
|
|
||||||
def settings(user_id):
|
|
||||||
user = User.query.get_or_404(user_id)
|
|
||||||
if not (user == current_user or current_user.is_administrator):
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
redirect_location_on_post = g.pop(
|
|
||||||
'_nopaque_redirect_location_on_post',
|
|
||||||
url_for('.settings', user_id=user_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
update_account_information_form = UpdateAccountInformationForm(user)
|
|
||||||
update_profile_information_form = UpdateProfileInformationForm(user)
|
|
||||||
update_avatar_form = UpdateAvatarForm()
|
|
||||||
update_password_form = UpdatePasswordForm(user)
|
|
||||||
update_notifications_form = UpdateNotificationsForm(user)
|
|
||||||
|
|
||||||
# region handle update profile information form
|
|
||||||
if update_profile_information_form.submit.data and update_profile_information_form.validate():
|
|
||||||
user.about_me = update_profile_information_form.about_me.data
|
|
||||||
user.location = update_profile_information_form.location.data
|
|
||||||
user.organization = update_profile_information_form.organization.data
|
|
||||||
user.website = update_profile_information_form.website.data
|
|
||||||
user.full_name = update_profile_information_form.full_name.data
|
|
||||||
db.session.commit()
|
|
||||||
flash('Your changes have been saved')
|
|
||||||
return redirect(redirect_location_on_post)
|
|
||||||
# endregion handle update profile information form
|
|
||||||
|
|
||||||
# region handle update avatar form
|
|
||||||
if update_avatar_form.submit.data and update_avatar_form.validate():
|
|
||||||
try:
|
|
||||||
Avatar.create(
|
|
||||||
update_avatar_form.avatar.data,
|
|
||||||
user=user
|
|
||||||
)
|
|
||||||
except (AttributeError, OSError):
|
|
||||||
abort(500)
|
|
||||||
db.session.commit()
|
|
||||||
flash('Your changes have been saved')
|
|
||||||
return redirect(redirect_location_on_post)
|
|
||||||
# endregion handle update avatar form
|
|
||||||
|
|
||||||
# region handle update account information form
|
|
||||||
if update_account_information_form.submit.data and update_account_information_form.validate():
|
|
||||||
user.email = update_account_information_form.email.data
|
|
||||||
user.username = update_account_information_form.username.data
|
|
||||||
db.session.commit()
|
|
||||||
flash('Profile settings updated')
|
|
||||||
return redirect(redirect_location_on_post)
|
|
||||||
# endregion handle update account information form
|
|
||||||
|
|
||||||
# region handle update password form
|
|
||||||
if update_password_form.submit.data and update_password_form.validate():
|
|
||||||
user.password = update_password_form.new_password.data
|
|
||||||
db.session.commit()
|
|
||||||
flash('Your changes have been saved')
|
|
||||||
return redirect(redirect_location_on_post)
|
|
||||||
# endregion handle update password form
|
|
||||||
|
|
||||||
# region handle update notifications form
|
|
||||||
if update_notifications_form.submit.data and update_notifications_form.validate():
|
|
||||||
user.setting_job_status_mail_notification_level = \
|
|
||||||
update_notifications_form.job_status_mail_notification_level.data
|
|
||||||
db.session.commit()
|
|
||||||
flash('Your changes have been saved')
|
|
||||||
return redirect(redirect_location_on_post)
|
|
||||||
# endregion handle update notifications form
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
'users/settings/settings.html.j2',
|
|
||||||
title='Settings',
|
|
||||||
update_account_information_form=update_account_information_form,
|
|
||||||
update_avatar_form=update_avatar_form,
|
|
||||||
update_notifications_form=update_notifications_form,
|
|
||||||
update_password_form=update_password_form,
|
|
||||||
update_profile_information_form=update_profile_information_form,
|
|
||||||
user=user
|
|
||||||
)
|
|
@ -42,9 +42,8 @@ def resource_after_delete(mapper, connection, resource):
|
|||||||
'path': resource.jsonpatch_path
|
'path': resource.jsonpatch_path
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{resource.user_hashid}'
|
room = f'/users/{resource.user_hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def cfa_after_delete(mapper, connection, cfa):
|
def cfa_after_delete(mapper, connection, cfa):
|
||||||
@ -55,9 +54,8 @@ def cfa_after_delete(mapper, connection, cfa):
|
|||||||
'path': jsonpatch_path
|
'path': jsonpatch_path
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{cfa.corpus.user.hashid}'
|
room = f'/users/{cfa.corpus.user.hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def resource_after_insert(mapper, connection, resource):
|
def resource_after_insert(mapper, connection, resource):
|
||||||
@ -71,9 +69,8 @@ def resource_after_insert(mapper, connection, resource):
|
|||||||
'value': jsonpatch_value
|
'value': jsonpatch_value
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{resource.user_hashid}'
|
room = f'/users/{resource.user_hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def cfa_after_insert(mapper, connection, cfa):
|
def cfa_after_insert(mapper, connection, cfa):
|
||||||
@ -86,9 +83,8 @@ def cfa_after_insert(mapper, connection, cfa):
|
|||||||
'value': jsonpatch_value
|
'value': jsonpatch_value
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{cfa.corpus.user.hashid}'
|
room = f'/users/{cfa.corpus.user.hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def resource_after_update(mapper, connection, resource):
|
def resource_after_update(mapper, connection, resource):
|
||||||
@ -113,9 +109,8 @@ def resource_after_update(mapper, connection, resource):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if jsonpatch:
|
if jsonpatch:
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{resource.user_hashid}'
|
room = f'/users/{resource.user_hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def job_after_update(mapper, connection, job):
|
def job_after_update(mapper, connection, job):
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
from flask import current_app, Flask
|
|
||||||
from flask_login import current_user
|
|
||||||
from flask_socketio import join_room, leave_room, Namespace
|
|
||||||
from app import db, hashids, socketio
|
|
||||||
from app.decorators import socketio_login_required
|
|
||||||
from app.models import User
|
|
||||||
|
|
||||||
|
|
||||||
def _delete_user(app: Flask, user_id: int):
|
|
||||||
with app.app_context():
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
user.delete()
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not Found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'body': user.to_json_serializeable(
|
|
||||||
backrefs=True,
|
|
||||||
relationships=True
|
|
||||||
),
|
|
||||||
'status': 200,
|
|
||||||
'statusText': 'OK'
|
|
||||||
}
|
|
||||||
|
|
||||||
@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):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not Found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
join_room(f'/users/{user.hashid}')
|
|
||||||
|
|
||||||
return {'status': 200, 'statusText': 'OK'}
|
|
||||||
|
|
||||||
@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):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not Found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
leave_room(f'/users/{user.hashid}')
|
|
||||||
|
|
||||||
return {'status': 200, 'statusText': 'OK'}
|
|
||||||
|
|
||||||
@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):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not Found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
socketio.start_background_task(
|
|
||||||
_delete_user,
|
|
||||||
current_app._get_current_object(),
|
|
||||||
user.id
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'body': f'User "{user.username}" marked for deletion',
|
|
||||||
'status': 202,
|
|
||||||
'statusText': 'Accepted'
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ nopaque.app.Client = class Client {
|
|||||||
// Endpoints
|
// Endpoints
|
||||||
this.corpora = new nopaque.app.endpoints.Corpora(this);
|
this.corpora = new nopaque.app.endpoints.Corpora(this);
|
||||||
this.jobs = new nopaque.app.endpoints.Jobs(this);
|
this.jobs = new nopaque.app.endpoints.Jobs(this);
|
||||||
|
this.settings = new nopaque.app.endpoints.Settings(this);
|
||||||
this.users = new nopaque.app.endpoints.Users(this);
|
this.users = new nopaque.app.endpoints.Users(this);
|
||||||
|
|
||||||
// Extensions
|
// Extensions
|
||||||
|
77
app/static/js/app/endpoints/settings.js
Normal file
77
app/static/js/app/endpoints/settings.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
nopaque.app.endpoints.Settings = class Settings {
|
||||||
|
constructor(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProfileIsPublic(newValue) {
|
||||||
|
const options = {
|
||||||
|
body: JSON.stringify(newValue),
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
method: 'PUT',
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`/settings/profile-is-public`, options);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProfileShowEmail(newValue) {
|
||||||
|
const options = {
|
||||||
|
body: JSON.stringify(newValue),
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
method: 'PUT',
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`/settings/profile-show-email`, options);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProfileShowLastSeen(newValue) {
|
||||||
|
const options = {
|
||||||
|
body: JSON.stringify(newValue),
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
method: 'PUT',
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`/settings/profile-show-last-seen`, options);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProfileShowMemberSince(newValue) {
|
||||||
|
const options = {
|
||||||
|
body: JSON.stringify(newValue),
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
method: 'PUT',
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`/settings/profile-show-member-since`, options);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
@ -1,43 +1,52 @@
|
|||||||
nopaque.app.endpoints.Users = class Users {
|
nopaque.app.endpoints.Users = class Users {
|
||||||
constructor(app) {
|
constructor(app) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
|
||||||
this.socket = io('/users', {transports: ['websocket'], upgrade: false});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id) {
|
async get(userId) {
|
||||||
const response = await this.socket.emitWithAck('get', id);
|
const options = {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (response.status !== 200) {
|
const response = await fetch(`/users/${userId}`, options);
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
const data = await response.json();
|
||||||
}
|
|
||||||
|
|
||||||
return response.body;
|
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async subscribe(id) {
|
async subscribe(userId) {
|
||||||
const response = await this.socket.emitWithAck('subscribe', id);
|
const response = await this.app.socket.emitWithAck('SUBSCRIBE User', userId);
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async unsubscribe(id) {
|
async unsubscribe(userId) {
|
||||||
const response = await this.socket.emitWithAck('unsubscribe', id);
|
const response = await this.app.socket.emitWithAck('UNSUBSCRIBE User', userId);
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id) {
|
async delete(userId) {
|
||||||
const response = await this.socket.emitWithAck('delete', id);
|
const options = {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json'
|
||||||
|
},
|
||||||
|
method: 'DELETE'
|
||||||
|
};
|
||||||
|
|
||||||
if (response.status != 202) {
|
const response = await fetch(`/users/${userId}`, options);
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
const data = await response.json();
|
||||||
}
|
|
||||||
|
|
||||||
return response.body;
|
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ nopaque.app.extensions.UserHub = class UserHub extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.app.users.socket.on('patch', (patch) => {this.#onPatch(patch)});
|
this.app.socket.on('PATCH', (patch) => {this.#onPatch(patch)});
|
||||||
}
|
}
|
||||||
|
|
||||||
add(userId) {
|
add(userId) {
|
||||||
|
@ -44,8 +44,8 @@
|
|||||||
Your profile
|
Your profile
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li {% if request.path == url_for('settings.settings') %}class="active"{% endif %}>
|
<li {% if request.path == url_for('settings.index') %}class="active"{% endif %}>
|
||||||
<a href="{{ url_for('settings.settings') }}">
|
<a href="{{ url_for('settings.index') }}">
|
||||||
<i class="material-icons">settings</i>
|
<i class="material-icons">settings</i>
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
'js/app/endpoints/index.js',
|
'js/app/endpoints/index.js',
|
||||||
'js/app/endpoints/corpora.js',
|
'js/app/endpoints/corpora.js',
|
||||||
'js/app/endpoints/jobs.js',
|
'js/app/endpoints/jobs.js',
|
||||||
|
'js/app/endpoints/settings.js',
|
||||||
'js/app/endpoints/users.js',
|
'js/app/endpoints/users.js',
|
||||||
'js/app/extensions/index.js',
|
'js/app/extensions/index.js',
|
||||||
'js/app/extensions/toaster.js',
|
'js/app/extensions/toaster.js',
|
||||||
|
@ -85,8 +85,8 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
{# settings #}
|
{# settings #}
|
||||||
<li {% if request.path == url_for('settings.settings') %}class="active"{% endif %}>
|
<li {% if request.path == url_for('settings.index') %}class="active"{% endif %}>
|
||||||
<a class="waves-effect" href="{{ url_for('settings.settings') }}"><i class="material-icons">settings</i>Settings</a>
|
<a class="waves-effect" href="{{ url_for('settings.index') }}"><i class="material-icons">settings</i>Settings</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{# log out #}
|
{# log out #}
|
||||||
|
@ -46,19 +46,19 @@
|
|||||||
<div class="row" style="margin-left: 24px;">
|
<div class="row" style="margin-left: 24px;">
|
||||||
<div class="col s12 l3">
|
<div class="col s12 l3">
|
||||||
<label>
|
<label>
|
||||||
<input {% if user.has_profile_privacy_setting('SHOW_EMAIL') %}checked{% endif %} class="profile-privacy-setting-checkbox" data-profile-privacy-setting-name="SHOW_EMAIL" {% if not user.is_public %}disabled{% endif %} type="checkbox">
|
<input {% if user.has_profile_privacy_setting('SHOW_EMAIL') %}checked{% endif %} class="profile-privacy-setting-checkbox" data-profile-privacy-setting-name="ProfileShowEmail" {% if not user.is_public %}disabled{% endif %} type="checkbox">
|
||||||
<span>Email</span>
|
<span>Email</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 l3">
|
<div class="col s12 l3">
|
||||||
<label>
|
<label>
|
||||||
<input {% if user.has_profile_privacy_setting('SHOW_LAST_SEEN') %}checked{% endif %} class="profile-privacy-setting-checkbox" data-profile-privacy-setting-name="SHOW_LAST_SEEN" {% if not user.is_public %}disabled{% endif %} type="checkbox">
|
<input {% if user.has_profile_privacy_setting('SHOW_LAST_SEEN') %}checked{% endif %} class="profile-privacy-setting-checkbox" data-profile-privacy-setting-name="ProfileShowLastSeen" {% if not user.is_public %}disabled{% endif %} type="checkbox">
|
||||||
<span>Last seen</span>
|
<span>Last seen</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 l3">
|
<div class="col s12 l3">
|
||||||
<label>
|
<label>
|
||||||
<input {% if user.has_profile_privacy_setting('SHOW_MEMBER_SINCE') %}checked{% endif %} class="profile-privacy-setting-checkbox" data-profile-privacy-setting-name="SHOW_MEMBER_SINCE" {% if not user.is_public %}disabled{% endif %} type="checkbox">
|
<input {% if user.has_profile_privacy_setting('SHOW_MEMBER_SINCE') %}checked{% endif %} class="profile-privacy-setting-checkbox" data-profile-privacy-setting-name="ProfileShowMemberSince" {% if not user.is_public %}disabled{% endif %} type="checkbox">
|
||||||
<span>Member since</span>
|
<span>Member since</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -74,7 +74,7 @@
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
{{ update_profile_information_form.hidden_tag() }}
|
{{ update_profile_information_form.hidden_tag() }}
|
||||||
{{ wtf.render_field(update_profile_information_form.full_name, material_icon='badge') }}
|
{{ wtf.render_field(update_profile_information_form.full_name, material_icon='badge') }}
|
||||||
{{ wtf.render_field(update_profile_information_form.about_me, material_icon='description', id='about-me-textfield') }}
|
{{ wtf.render_field(update_profile_information_form.about_me, material_icon='description') }}
|
||||||
{{ wtf.render_field(update_profile_information_form.website, material_icon='laptop') }}
|
{{ wtf.render_field(update_profile_information_form.website, material_icon='laptop') }}
|
||||||
{{ wtf.render_field(update_profile_information_form.organization, material_icon='business') }}
|
{{ wtf.render_field(update_profile_information_form.organization, material_icon='business') }}
|
||||||
{{ wtf.render_field(update_profile_information_form.location, material_icon='location_on') }}
|
{{ wtf.render_field(update_profile_information_form.location, material_icon='location_on') }}
|
||||||
@ -252,28 +252,28 @@ for (let collapsibleElement of document.querySelectorAll('.collapsible.no-autoin
|
|||||||
// #region Profile Privacy settings
|
// #region Profile Privacy settings
|
||||||
let profileIsPublicSwitchElement = document.querySelector('#profile-is-public-switch');
|
let profileIsPublicSwitchElement = document.querySelector('#profile-is-public-switch');
|
||||||
let profilePrivacySettingCheckboxElements = document.querySelectorAll('.profile-privacy-setting-checkbox');
|
let profilePrivacySettingCheckboxElements = document.querySelectorAll('.profile-privacy-setting-checkbox');
|
||||||
profileIsPublicSwitchElement.addEventListener('change', (event) => {
|
profileIsPublicSwitchElement.addEventListener('change', async (event) => {
|
||||||
let newEnabled = profileIsPublicSwitchElement.checked;
|
const newEnabled = profileIsPublicSwitchElement.checked;
|
||||||
nopaque.requests.users.entity.settings.profilePrivacy.update({{ user.hashid|tojson }}, 'is-public', newEnabled)
|
try {
|
||||||
.then(
|
const message = await app.settings.updateProfileIsPublic(newEnabled);
|
||||||
(response) => {
|
for (let profilePrivacySettingCheckboxElement of profilePrivacySettingCheckboxElements) {
|
||||||
for (let profilePrivacySettingCheckboxElement of document.querySelectorAll('.profile-privacy-setting-checkbox')) {
|
profilePrivacySettingCheckboxElement.disabled = !newEnabled;
|
||||||
profilePrivacySettingCheckboxElement.disabled = !newEnabled;
|
}
|
||||||
}
|
app.ui.flash(message);
|
||||||
},
|
} catch (e) {
|
||||||
(response) => {
|
profileIsPublicSwitchElement.checked = !newEnabled;
|
||||||
profileIsPublicSwitchElement.checked = !newEnabled;
|
app.ui.flash(e.message, 'error');
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
});
|
||||||
for (let profilePrivacySettingCheckboxElement of profilePrivacySettingCheckboxElements) {
|
for (let profilePrivacySettingCheckboxElement of profilePrivacySettingCheckboxElements) {
|
||||||
profilePrivacySettingCheckboxElement.addEventListener('change', (event) => {
|
profilePrivacySettingCheckboxElement.addEventListener('change', async (event) => {
|
||||||
let newEnabled = profilePrivacySettingCheckboxElement.checked;
|
const newEnabled = profilePrivacySettingCheckboxElement.checked;
|
||||||
let valueName = profilePrivacySettingCheckboxElement.dataset.profilePrivacySettingName;
|
const valueName = profilePrivacySettingCheckboxElement.dataset.profilePrivacySettingName;
|
||||||
nopaque.requests.users.entity.settings.profilePrivacy.update({{ user.hashid|tojson }}, valueName, newEnabled)
|
try {
|
||||||
.catch((response) => {
|
app.settings[`update${valueName}`](newEnabled)
|
||||||
|
} catch (error) {
|
||||||
profilePrivacySettingCheckboxElement.checked = !newEnabled;
|
profilePrivacySettingCheckboxElement.checked = !newEnabled;
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// #endregion Profile Privacy settings
|
// #endregion Profile Privacy settings
|
Loading…
Reference in New Issue
Block a user