mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
user banning and purging
This commit is contained in:
parent
4b916fcf86
commit
56d09b264a
17 changed files with 285 additions and 69 deletions
|
@ -138,6 +138,8 @@ def user_profile(actor):
|
|||
return resp
|
||||
else:
|
||||
return show_profile(user)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route('/c/<actor>', methods=['GET'])
|
||||
|
@ -313,38 +315,39 @@ def shared_inbox():
|
|||
user_ap_id = request_json['object']['actor']
|
||||
liked_ap_id = request_json['object']['object']
|
||||
user = find_actor_or_create(user_ap_id)
|
||||
vote_weight = 1.0
|
||||
if user.ap_domain:
|
||||
instance = Instance.query.filter_by(domain=user.ap_domain).fetch()
|
||||
if instance:
|
||||
vote_weight = instance.vote_weight
|
||||
liked = find_liked_object(liked_ap_id)
|
||||
# insert into voted table
|
||||
if liked is not None and isinstance(liked, Post):
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=liked.id).first()
|
||||
if existing_vote:
|
||||
existing_vote.effect = vote_effect * vote_weight
|
||||
if user:
|
||||
vote_weight = 1.0
|
||||
if user.ap_domain:
|
||||
instance = Instance.query.filter_by(domain=user.ap_domain).fetch()
|
||||
if instance:
|
||||
vote_weight = instance.vote_weight
|
||||
liked = find_liked_object(liked_ap_id)
|
||||
# insert into voted table
|
||||
if liked is not None and isinstance(liked, Post):
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=liked.id).first()
|
||||
if existing_vote:
|
||||
existing_vote.effect = vote_effect * vote_weight
|
||||
else:
|
||||
vote = PostVote(user_id=user.id, author_id=liked.user_id, post_id=liked.id,
|
||||
effect=vote_effect * vote_weight)
|
||||
db.session.add(vote)
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
elif liked is not None and isinstance(liked, PostReply):
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=liked.id).first()
|
||||
if existing_vote:
|
||||
existing_vote.effect = vote_effect * vote_weight
|
||||
else:
|
||||
vote = PostReplyVote(user_id=user.id, author_id=liked.user_id, post_reply_id=liked.id,
|
||||
effect=vote_effect * vote_weight)
|
||||
db.session.add(vote)
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
vote = PostVote(user_id=user.id, author_id=liked.user_id, post_id=liked.id,
|
||||
effect=vote_effect * vote_weight)
|
||||
db.session.add(vote)
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
elif liked is not None and isinstance(liked, PostReply):
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=liked.id).first()
|
||||
if existing_vote:
|
||||
existing_vote.effect = vote_effect * vote_weight
|
||||
else:
|
||||
vote = PostReplyVote(user_id=user.id, author_id=liked.user_id, post_reply_id=liked.id,
|
||||
effect=vote_effect * vote_weight)
|
||||
db.session.add(vote)
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
activity_log.exception_message = 'Could not detect type of like'
|
||||
if activity_log.result == 'success':
|
||||
... # todo: recalculate 'hotness' of liked post/reply
|
||||
# todo: if vote was on content in local community, federate the vote out to followers
|
||||
activity_log.exception_message = 'Could not detect type of like'
|
||||
if activity_log.result == 'success':
|
||||
... # todo: recalculate 'hotness' of liked post/reply
|
||||
# todo: if vote was on content in local community, federate the vote out to followers
|
||||
|
||||
# Follow: remote user wants to follow one of our communities
|
||||
elif request_json['type'] == 'Follow': # Follow is when someone wants to join a community
|
||||
|
@ -399,14 +402,15 @@ def shared_inbox():
|
|||
user_ap_id = request_json['object']['actor']
|
||||
user = find_actor_or_create(user_ap_id)
|
||||
community = find_actor_or_create(community_ap_id)
|
||||
join_request = CommunityJoinRequest.query.filter_by(user_id=user.id,
|
||||
community_id=community.id).first()
|
||||
if join_request:
|
||||
member = CommunityMember(user_id=user.id, community_id=community.id)
|
||||
db.session.add(member)
|
||||
community.subscriptions_count += 1
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
if user and community:
|
||||
join_request = CommunityJoinRequest.query.filter_by(user_id=user.id,
|
||||
community_id=community.id).first()
|
||||
if join_request:
|
||||
member = CommunityMember(user_id=user.id, community_id=community.id)
|
||||
db.session.add(member)
|
||||
community.subscriptions_count += 1
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
activity_log.exception_message = 'Instance banned'
|
||||
else:
|
||||
|
|
|
@ -192,7 +192,7 @@ def find_actor_or_create(actor: str) -> Union[User, Community, None]:
|
|||
ap_profile_id=actor).first() # finds communities formatted like https://localhost/c/*
|
||||
|
||||
if current_app.config['SERVER_NAME'] + '/u/' in actor:
|
||||
user = User.query.filter_by(username=actor.split('/')[-1], ap_id=None).first() # finds local users
|
||||
user = User.query.filter_by(username=actor.split('/')[-1], ap_id=None, banned=False).first() # finds local users
|
||||
if user is None:
|
||||
return None
|
||||
elif actor.startswith('https://'):
|
||||
|
@ -201,6 +201,8 @@ def find_actor_or_create(actor: str) -> Union[User, Community, None]:
|
|||
return None
|
||||
user = User.query.filter_by(
|
||||
ap_profile_id=actor).first() # finds users formatted like https://kbin.social/u/tables
|
||||
if user.banned:
|
||||
return None
|
||||
if user is None:
|
||||
user = Community.query.filter_by(ap_profile_id=actor).first()
|
||||
if user is None:
|
||||
|
|
|
@ -48,4 +48,4 @@ class ResetPasswordForm(FlaskForm):
|
|||
password2 = PasswordField(
|
||||
_l('Repeat password'), validators=[DataRequired(),
|
||||
EqualTo('password')])
|
||||
submit = SubmitField(_l('Request password reset'))
|
||||
submit = SubmitField(_l('Set password'))
|
||||
|
|
|
@ -86,11 +86,13 @@ def register(app):
|
|||
|
||||
staff_role = Role(name='Staff', weight=2)
|
||||
staff_role.permissions.append(RolePermission(permission='approve registrations'))
|
||||
staff_role.permissions.append(RolePermission(permission='manage users'))
|
||||
staff_role.permissions.append(RolePermission(permission='ban users'))
|
||||
db.session.add(staff_role)
|
||||
|
||||
admin_role = Role(name='Admin', weight=3)
|
||||
admin_role.permissions.append(RolePermission(permission='approve registrations'))
|
||||
admin_role.permissions.append(RolePermission(permission='change user roles'))
|
||||
admin_role.permissions.append(RolePermission(permission='ban users'))
|
||||
admin_role.permissions.append(RolePermission(permission='manage users'))
|
||||
db.session.add(admin_role)
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ from hashlib import md5
|
|||
from time import time
|
||||
from typing import List
|
||||
|
||||
from flask import current_app, escape
|
||||
from flask import current_app, escape, url_for
|
||||
from flask_login import UserMixin
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import or_, text
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_babel import _, lazy_gettext as _l
|
||||
from sqlalchemy.orm import backref
|
||||
|
@ -38,6 +38,7 @@ class Community(db.Model):
|
|||
id = db.Column(db.Integer, primary_key=True)
|
||||
icon_id = db.Column(db.Integer, db.ForeignKey('file.id'))
|
||||
image_id = db.Column(db.Integer, db.ForeignKey('file.id'))
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||
name = db.Column(db.String(256), index=True)
|
||||
title = db.Column(db.String(256))
|
||||
description = db.Column(db.Text)
|
||||
|
@ -185,6 +186,12 @@ class User(UserMixin, db.Model):
|
|||
except Exception:
|
||||
return False
|
||||
|
||||
def display_name(self):
|
||||
if self.deleted is False:
|
||||
return self.user_name
|
||||
else:
|
||||
return '[deleted]'
|
||||
|
||||
def avatar(self, size):
|
||||
digest = md5(self.email.lower().encode('utf-8')).hexdigest()
|
||||
return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
|
||||
|
@ -266,6 +273,30 @@ class User(UserMixin, db.Model):
|
|||
return
|
||||
return User.query.get(id)
|
||||
|
||||
def purge_content(self):
|
||||
db.session.query(ActivityLog).filter(ActivityLog.user_id == self.id).delete()
|
||||
db.session.query(PostVote).filter(PostVote.user_id == self.id).delete()
|
||||
db.session.query(PostReplyVote).filter(PostReplyVote.user_id == self.id).delete()
|
||||
db.session.query(PostReply).filter(PostReply.user_id == self.id).delete()
|
||||
db.session.query(FilterKeyword).filter(FilterKeyword.user_id == self.id).delete()
|
||||
db.session.query(Filter).filter(Filter.user_id == self.id).delete()
|
||||
db.session.query(DomainBlock).filter(DomainBlock.user_id == self.id).delete()
|
||||
db.session.query(CommunityJoinRequest).filter(CommunityJoinRequest.user_id == self.id).delete()
|
||||
db.session.query(CommunityMember).filter(CommunityMember.user_id == self.id).delete()
|
||||
db.session.query(CommunityBlock).filter(CommunityBlock.user_id == self.id).delete()
|
||||
db.session.query(CommunityBan).filter(CommunityBan.user_id == self.id).delete()
|
||||
db.session.query(Community).filter(Community.user_id == self.id).delete()
|
||||
db.session.query(Post).filter(Post.user_id == self.id).delete()
|
||||
db.session.query(UserNote).filter(UserNote.user_id == self.id).delete()
|
||||
db.session.query(UserNote).filter(UserNote.target_id == self.id).delete()
|
||||
db.session.query(UserFollowRequest).filter(UserFollowRequest.follow_id == self.id).delete()
|
||||
db.session.query(UserFollowRequest).filter(UserFollowRequest.user_id == self.id).delete()
|
||||
db.session.query(UserBlock).filter(UserBlock.blocked_id == self.id).delete()
|
||||
db.session.query(UserBlock).filter(UserBlock.blocker_id == self.id).delete()
|
||||
db.session.execute(text('DELETE FROM user_role WHERE user_id = :user_id'),
|
||||
{'user_id': self.id})
|
||||
|
||||
|
||||
|
||||
class ActivityLog(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
document.addEventListener("DOMContentLoaded", function () {
|
||||
setupCommunityNameInput();
|
||||
setupShowMoreLinks();
|
||||
setupConfirmFirst();
|
||||
});
|
||||
|
||||
|
||||
|
@ -11,6 +12,17 @@ window.addEventListener("load", function () {
|
|||
setupHideButtons();
|
||||
});
|
||||
|
||||
// every element with the 'confirm_first' class gets a popup confirmation dialog
|
||||
function setupConfirmFirst() {
|
||||
const show_first = document.querySelectorAll('.confirm_first');
|
||||
show_first.forEach(element => {
|
||||
element.addEventListener("click", function(event) {
|
||||
if (!confirm("Are you sure?")) {
|
||||
event.preventDefault(); // As the user clicked "Cancel" in the dialog, prevent the default action.
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
function setupShowMoreLinks() {
|
||||
const comments = document.querySelectorAll('.comment');
|
||||
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
{% macro render_username(user) %}
|
||||
{% if user.deleted %}
|
||||
[deleted]
|
||||
{% else %}
|
||||
<a href="{{ url_for('activitypub.user_profile', actor=user.user_name) }}">{{ user.user_name }}</a>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
{% endif %}
|
||||
</small></p>
|
||||
{% endif %}
|
||||
<p><small>submitted {{ moment(post.posted_at).fromNow() }} by {{ post.author.user_name }}</small></p>
|
||||
<p><small>submitted {{ moment(post.posted_at).fromNow() }} by {{ render_username(post.author) }}</small></p>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
{% if post.url %}
|
||||
|
@ -47,7 +47,7 @@
|
|||
</small></p>
|
||||
{% endif %}
|
||||
<p class="small">submitted {{ moment(post.posted_at).fromNow() }} by
|
||||
<a href="{{ url_for('activitypub.user_profile', actor=post.author.user_name) }}">{{ post.author.user_name }}</a>
|
||||
{{ render_username(post.author) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="post_teaser">
|
||||
<div class="row meta_row small">
|
||||
<div class="col"><a href="{{ url_for('activitypub.user_profile', actor=post.author.user_name) }}">{{ post.author.user_name }}</a> · {{ moment(post.posted_at).fromNow() }}</div>
|
||||
<div class="col">{{ render_username(post.author) }} · {{ moment(post.posted_at).fromNow() }}</div>
|
||||
</div>
|
||||
<div class="row main_row">
|
||||
<div class="col{% if post.image_id %}-8{% endif %}">
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<h3>Moderators</h3>
|
||||
<ol>
|
||||
{% for mod in mods %}
|
||||
<li><a href="/u/{{ mod.user_name }}">{{ mod.user_name }}</a></li>
|
||||
<li>{{ render_username(mod) }}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endif %}
|
||||
|
|
|
@ -18,12 +18,16 @@
|
|||
</div>
|
||||
<div class="hide_button"><a href='#'>[-] hide</a></div>
|
||||
<div class="comment_author">
|
||||
{% if comment['comment'].author.avatar_id %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.ap_id }}">
|
||||
<img src="{{ comment['comment'].author.avatar_image() }}" alt="Avatar" /></a>
|
||||
{% endif %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.link() }}">
|
||||
<strong>{{ comment['comment'].author.user_name}}</strong></a>
|
||||
{% if comment['comment'].author.deleted %}
|
||||
<strong>[deleted]</strong>
|
||||
{% else %}
|
||||
{% if comment['comment'].author.avatar_id %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.ap_id }}">
|
||||
<img src="{{ comment['comment'].author.avatar_image() }}" alt="Avatar" /></a>
|
||||
{% endif %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.link() }}">
|
||||
<strong>{{ comment['comment'].author.user_name}}</strong></a>
|
||||
{% endif %}
|
||||
{% if comment['comment'].author.id == post.author.id%}<span title="Submitter of original post" aria-label="submitter">[S]</span>{% endif %}
|
||||
<span class="text-muted small">{{ moment(comment['comment'].posted_at).fromNow(refresh=True) }}</span>
|
||||
</div>
|
||||
|
|
|
@ -30,12 +30,16 @@
|
|||
</div>
|
||||
<div class="hide_button"><a href='#'>[-] hide</a></div>
|
||||
<div class="comment_author">
|
||||
{% if comment['comment'].author.avatar_id %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.ap_id }}">
|
||||
<img src="{{ comment['comment'].author.avatar_image() }}" alt="Avatar" /></a>
|
||||
{% if comment['comment'].author.deleted %}
|
||||
<strong>[deleted]</strong>
|
||||
{% else %}
|
||||
{% if comment['comment'].author.avatar_id %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.ap_id }}">
|
||||
<img src="{{ comment['comment'].author.avatar_image() }}" alt="Avatar" /></a>
|
||||
{% endif %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.link() }}">
|
||||
<strong>{{ comment['comment'].author.user_name }}</strong></a>
|
||||
{% endif %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.link() }}">
|
||||
<strong>{{ comment['comment'].author.user_name }}</strong></a>
|
||||
{% if comment['comment'].author.id == post.author.id %}<span title="Submitter of original post" aria-label="submitter">[S]</span>{% endif %}
|
||||
<span class="text-muted small">{{ moment(comment['comment'].posted_at).fromNow(refresh=True) }}</span>
|
||||
</div>
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
{% if current_user.id == user.id %}
|
||||
{% if current_user.is_authenticated and current_user.id == user.id %}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('Manage') }}</h2>
|
||||
|
@ -83,16 +83,37 @@
|
|||
<ol>
|
||||
{% for community in moderates %}
|
||||
<li>
|
||||
<a href="/c/{{ community.link() }}">
|
||||
<img src="{{ community.icon_image() }}" class="community_icon rounded-circle" loading="lazy" />
|
||||
{{ community.display_name() }}
|
||||
</a>
|
||||
<a href="/c/{{ community.link() }}"><img src="{{ community.icon_image() }}" class="community_icon rounded-circle" loading="lazy" />{{ community.display_name() }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated and (user_access('ban users', current_user.id) or user_access('manage users', current_user.id)) and user.id != current_user.id %}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('Crush') }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% if user_access('ban users', current_user.id) %}
|
||||
<div class="col-4">
|
||||
<a class="w-100 btn btn-primary confirm_first" href="/u/{{ user.user_name }}/ban">{{ _('Ban') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if user_access('manage users', current_user.id) %}
|
||||
<div class="col-4">
|
||||
<a class="w-100 btn btn-primary confirm_first" href="/u/{{ user.user_name }}/delete">{{ _('Delete') }}</a>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<a class="w-100 btn btn-primary confirm_first" href="/u/{{ user.user_name }}/ban_purge">{{ _('Ban + Purge') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,14 +6,16 @@ from app import db
|
|||
from app.models import Post, Community, CommunityMember, User, PostReply
|
||||
from app.user import bp
|
||||
from app.user.forms import ProfileForm, SettingsForm
|
||||
from app.utils import get_setting, render_template, markdown_to_html
|
||||
from app.utils import get_setting, render_template, markdown_to_html, user_access
|
||||
from sqlalchemy import desc, or_
|
||||
|
||||
|
||||
def show_profile(user):
|
||||
if user.deleted or user.banned and current_user.is_anonymous():
|
||||
abort(404)
|
||||
posts = Post.query.filter_by(user_id=user.id).order_by(desc(Post.posted_at)).all()
|
||||
moderates = Community.query.filter_by(banned=False).join(CommunityMember).filter(or_(CommunityMember.is_moderator, CommunityMember.is_owner))
|
||||
if user.id != current_user.id:
|
||||
if current_user.is_anonymous or user.id != current_user.id:
|
||||
moderates = moderates.filter(Community.private_mods == False)
|
||||
post_replies = PostReply.query.filter_by(user_id=user.id).order_by(desc(PostReply.posted_at)).all()
|
||||
canonical = user.ap_public_url if user.ap_public_url else None
|
||||
|
@ -78,3 +80,80 @@ def change_settings(actor):
|
|||
form.manually_approves_followers.data = current_user.ap_manually_approves_followers
|
||||
|
||||
return render_template('user/edit_settings.html', title=_('Edit profile'), form=form, user=current_user)
|
||||
|
||||
|
||||
@bp.route('/u/<actor>/ban', methods=['GET'])
|
||||
def ban_profile(actor):
|
||||
if user_access('ban users', current_user.id):
|
||||
actor = actor.strip()
|
||||
user = User.query.filter_by(user_name=actor, deleted=False).first()
|
||||
if user is None:
|
||||
user = User.query.filter_by(ap_id=actor, deleted=False).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
|
||||
if user.id == current_user.id:
|
||||
flash('You cannot ban yourself.', 'error')
|
||||
else:
|
||||
user.banned = True
|
||||
db.session.commit()
|
||||
|
||||
flash(f'{actor} has been banned.')
|
||||
else:
|
||||
abort(401)
|
||||
|
||||
return redirect(f'/u/{actor}')
|
||||
|
||||
|
||||
@bp.route('/u/<actor>/delete', methods=['GET'])
|
||||
def delete_profile(actor):
|
||||
if user_access('manage users', current_user.id):
|
||||
actor = actor.strip()
|
||||
user = User.query.filter_by(user_name=actor, deleted=False).first()
|
||||
if user is None:
|
||||
user = User.query.filter_by(ap_id=actor, deleted=False).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
if user.id == current_user.id:
|
||||
flash('You cannot delete yourself.', 'error')
|
||||
else:
|
||||
user.banned = True
|
||||
user.deleted = True
|
||||
db.session.commit()
|
||||
|
||||
flash(f'{actor} has been deleted.')
|
||||
else:
|
||||
abort(401)
|
||||
|
||||
return redirect(f'/u/{actor}')
|
||||
|
||||
|
||||
@bp.route('/u/<actor>/ban_purge', methods=['GET'])
|
||||
def ban_purge_profile(actor):
|
||||
if user_access('manage users', current_user.id):
|
||||
actor = actor.strip()
|
||||
user = User.query.filter_by(user_name=actor, deleted=False).first()
|
||||
if user is None:
|
||||
user = User.query.filter_by(ap_id=actor, deleted=False).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
|
||||
if user.id == current_user.id:
|
||||
flash('You cannot purge yourself.', 'error')
|
||||
else:
|
||||
user.banned = True
|
||||
user.deleted = True
|
||||
db.session.commit()
|
||||
|
||||
user.purge_content()
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
|
||||
# todo: empty relevant caches
|
||||
# todo: federate deletion
|
||||
|
||||
flash(f'{actor} has been banned, deleted and all their content deleted.')
|
||||
else:
|
||||
abort(401)
|
||||
|
||||
return redirect(f'/u/{actor}')
|
||||
|
|
19
app/utils.py
19
app/utils.py
|
@ -8,8 +8,11 @@ from bs4 import BeautifulSoup
|
|||
import requests
|
||||
import os
|
||||
from flask import current_app, json
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import text
|
||||
|
||||
from app import db, cache
|
||||
from app.models import Settings, Domain, Instance, BannedInstances
|
||||
from app.models import Settings, Domain, Instance, BannedInstances, User
|
||||
|
||||
|
||||
# Flask's render_template function, with support for themes added
|
||||
|
@ -141,7 +144,10 @@ def html_to_markdown_worker(element, indent_level=0):
|
|||
|
||||
|
||||
def markdown_to_html(markdown_text) -> str:
|
||||
return allowlist_html(markdown2.markdown(markdown_text, safe_mode=True))
|
||||
if markdown_text:
|
||||
return allowlist_html(markdown2.markdown(markdown_text, safe_mode=True))
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def domain_from_url(url: str) -> Domain:
|
||||
|
@ -167,3 +173,12 @@ def digits(input: int) -> int:
|
|||
return 1 # Special case: 0 has 1 digit
|
||||
else:
|
||||
return math.floor(math.log10(abs(input))) + 1
|
||||
|
||||
|
||||
@cache.memoize(timeout=50)
|
||||
def user_access(permission: str, user_id: int) -> bool:
|
||||
has_access = db.session.execute(text('SELECT * FROM "role_permission" as rp ' +
|
||||
'INNER JOIN user_role ur on rp.role_id = ur.role_id ' +
|
||||
'WHERE ur.user_id = :user_id AND rp.permission = :permission'),
|
||||
{'user_id': user_id, 'permission': permission}).first()
|
||||
return has_access is not None
|
34
migrations/versions/c88bbba381b5_community_creator.py
Normal file
34
migrations/versions/c88bbba381b5_community_creator.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
"""community creator
|
||||
|
||||
Revision ID: c88bbba381b5
|
||||
Revises: 882e33231c5b
|
||||
Create Date: 2023-10-21 15:32:15.856895
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'c88bbba381b5'
|
||||
down_revision = '882e33231c5b'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('community', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('user_id', sa.Integer(), nullable=True))
|
||||
batch_op.create_foreign_key(None, 'user', ['user_id'], ['id'])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('community', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='foreignkey')
|
||||
batch_op.drop_column('user_id')
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -6,7 +6,7 @@ from app import create_app, db, cli
|
|||
import os, click
|
||||
from flask import session, g
|
||||
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE
|
||||
from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits
|
||||
from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits, user_access
|
||||
|
||||
app = create_app()
|
||||
cli.register(app)
|
||||
|
@ -29,6 +29,7 @@ with app.app_context():
|
|||
app.jinja_env.globals['len'] = len
|
||||
app.jinja_env.globals['digits'] = digits
|
||||
app.jinja_env.globals['str'] = str
|
||||
app.jinja_env.globals['user_access'] = user_access
|
||||
app.jinja_env.filters['shorten'] = shorten_string
|
||||
app.jinja_env.filters['shorten_url'] = shorten_url
|
||||
|
||||
|
|
Loading…
Reference in a new issue