From 0b159edf0ab29c3ab8a586e7b8adb1e1238d26ed Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:00:07 +1300 Subject: [PATCH] comment edit, delete, report, block --- app/activitypub/routes.py | 6 +- app/cli.py | 2 +- app/models.py | 3 +- app/post/routes.py | 126 +++++++++++++++++- app/static/structure.css | 1 + app/static/structure.scss | 1 + app/static/styles.css | 9 ++ app/static/styles.scss | 9 ++ app/templates/post/post.html | 9 +- app/templates/post/post_options.html | 6 +- app/templates/post/post_reply_edit.html | 85 ++++++++++++ app/templates/post/post_reply_options.html | 36 +++++ app/templates/post/post_reply_report.html | 18 +++ .../e8113bc01e3a_post_reply_instance.py | 36 +++++ regional.txt | 2 + 15 files changed, 337 insertions(+), 12 deletions(-) create mode 100644 app/templates/post/post_reply_edit.html create mode 100644 app/templates/post/post_reply_options.html create mode 100644 app/templates/post/post_reply_report.html create mode 100644 migrations/versions/e8113bc01e3a_post_reply_instance.py create mode 100644 regional.txt diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index 0a0186e5..1d58c09f 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -425,7 +425,8 @@ def process_inbox_request(request_json, activitypublog_id): score=instance_weight(user.ap_domain), ap_id=request_json['object']['id'], ap_create_id=request_json['id'], - ap_announce_id=None) + ap_announce_id=None, + instance_id=user.instance_id) if 'source' in request_json['object'] and \ request_json['object']['source']['mediaType'] == 'text/markdown': post_reply.body = request_json['object']['source']['content'] @@ -530,7 +531,8 @@ def process_inbox_request(request_json, activitypublog_id): nsfl=community.nsfl, ap_id=request_json['object']['object']['id'], ap_create_id=request_json['object']['id'], - ap_announce_id=request_json['id']) + ap_announce_id=request_json['id'], + instance_id=user.instance_id) if 'source' in request_json['object']['object'] and \ request_json['object']['object']['source']['mediaType'] == 'text/markdown': post_reply.body = request_json['object']['object']['source']['content'] diff --git a/app/cli.py b/app/cli.py index dade6ba1..4c57c68a 100644 --- a/app/cli.py +++ b/app/cli.py @@ -56,7 +56,7 @@ def register(app): db.create_all() private_key, public_key = RsaKeys.generate_keypair() db.session.add(Site(name="PieFed", description='', public_key=public_key, private_key=private_key)) - db.session.add(Instance(domain=app.config['SERVER_NAME'], software='PieFed')) + db.session.add(Instance(domain=app.config['SERVER_NAME'], software='PieFed')) # Instance 1 is always the local instance db.session.add(Settings(name='allow_nsfw', value=json.dumps(False))) db.session.add(Settings(name='allow_nsfl', value=json.dumps(False))) db.session.add(Settings(name='allow_dislike', value=json.dumps(True))) diff --git a/app/models.py b/app/models.py index 81193076..ad29473b 100644 --- a/app/models.py +++ b/app/models.py @@ -590,6 +590,7 @@ class PostReply(db.Model): parent_id = db.Column(db.Integer) root_id = db.Column(db.Integer) depth = db.Column(db.Integer, default=0) + instance_id = db.Column(db.Integer, db.ForeignKey('instance.id'), index=True) body = db.Column(db.Text) body_html = db.Column(db.Text) body_html_safe = db.Column(db.Boolean, default=False) @@ -738,6 +739,7 @@ class Instance(db.Model): updated_at = db.Column(db.DateTime, default=utcnow) posts = db.relationship('Post', backref='instance', lazy='dynamic') + post_replies = db.relationship('PostReply', backref='instance', lazy='dynamic') communities = db.relationship('Community', backref='instance', lazy='dynamic') @@ -850,7 +852,6 @@ class Report(db.Model): suspect_user_id = db.Column(db.Integer, db.ForeignKey('user.id')) suspect_post_id = db.Column(db.Integer, db.ForeignKey('post.id')) suspect_post_reply_id = db.Column(db.Integer, db.ForeignKey('post_reply.id')) - suspect_reply_id = db.Column(db.Integer, db.ForeignKey('post_reply.id')) created_at = db.Column(db.DateTime, default=utcnow) updated = db.Column(db.DateTime, default=utcnow) diff --git a/app/post/routes.py b/app/post/routes.py index 471e5a23..b75d6d54 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -450,6 +450,13 @@ def post_options(post_id: int): return render_template('post/post_options.html', post=post) +@bp.route('/post//comment//options', methods=['GET']) +def post_reply_options(post_id: int, comment_id: int): + post = Post.query.get_or_404(post_id) + post_reply = PostReply.query.get_or_404(comment_id) + return render_template('post/post_reply_options.html', post=post, post_reply=post_reply) + + @bp.route('/post//edit', methods=['GET', 'POST']) @login_required def post_edit(post_id: int): @@ -471,6 +478,7 @@ def post_edit(post_id: int): db.session.commit() post.flush_cache() flash(_('Your changes have been saved.'), 'success') + # todo: federate edit return redirect(url_for('activitypub.post_ap', post_id=post.id)) else: if post.type == constants.POST_TYPE_ARTICLE: @@ -513,7 +521,8 @@ def post_report(post_id: int): form = ReportPostForm() if form.validate_on_submit(): report = Report(reasons=form.reasons_to_string(form.reasons.data), description=form.description.data, - type=1, reporter_id=current_user.id, suspect_post_id=post.id) + type=1, reporter_id=current_user.id, suspect_user_id=post.author.id, suspect_post_id=post.id, + suspect_community_id=post.community.id) db.session.add(report) # Notify moderators @@ -591,3 +600,118 @@ def post_mea_culpa(post_id: int): return redirect(url_for('activitypub.post_ap', post_id=post.id)) return render_template('post/post_mea_culpa.html', title=_('I changed my mind'), form=form, post=post) + + +@bp.route('/post//comment//report', methods=['GET', 'POST']) +@login_required +def post_reply_report(post_id: int, comment_id: int): + post = Post.query.get_or_404(post_id) + post_reply = PostReply.query.get_or_404(comment_id) + form = ReportPostForm() + if form.validate_on_submit(): + report = Report(reasons=form.reasons_to_string(form.reasons.data), description=form.description.data, + type=1, reporter_id=current_user.id, suspect_post_id=post.id, suspect_community_id=post.community.id, + suspect_user_id=post_reply.author.id, suspect_post_reply_id=post_reply.id) + db.session.add(report) + + # Notify moderators + for mod in post.community.moderators(): + notification = Notification(user_id=mod.user_id, title=_('A comment has been reported'), + url=f"https://{current_app.config['SERVER_NAME']}/comment/{post_reply.id}", + author_id=current_user.id) + db.session.add(notification) + post_reply.reports += 1 + # todo: Also notify admins for certain types of report + db.session.commit() + + # todo: federate report to originating instance + if not post.community.is_local() and form.report_remote.data: + ... + + flash(_('Comment has been reported, thank you!')) + return redirect(post.community.local_url()) + elif request.method == 'GET': + form.report_remote.data = True + + return render_template('post/post_reply_report.html', title=_('Report comment'), form=form, post=post, post_reply=post_reply) + + +@bp.route('/post//comment//block_user', methods=['GET', 'POST']) +@login_required +def post_reply_block_user(post_id: int, comment_id: int): + post = Post.query.get_or_404(post_id) + post_reply = PostReply.query.get_or_404(comment_id) + existing = UserBlock.query.filter_by(blocker_id=current_user.id, blocked_id=post_reply.author.id).first() + if not existing: + db.session.add(UserBlock(blocker_id=current_user.id, blocked_id=post_reply.author.id)) + db.session.commit() + flash(_('%(name)s has been blocked.', name=post_reply.author.user_name)) + + # todo: federate block to post_reply author instance + + return redirect(url_for('activitypub.post_ap', post_id=post.id)) + + +@bp.route('/post//comment//block_instance', methods=['GET', 'POST']) +@login_required +def post_reply_block_instance(post_id: int, comment_id: int): + post = Post.query.get_or_404(post_id) + post_reply = PostReply.query.get_or_404(comment_id) + existing = InstanceBlock.query.filter_by(user_id=current_user.id, instance_id=post_reply.instance_id).first() + if not existing: + db.session.add(InstanceBlock(user_id=current_user.id, instance_id=post_reply.instance_id)) + db.session.commit() + flash(_('Content from %(name)s will be hidden.', name=post_reply.instance.domain)) + return redirect(url_for('activitypub.post_ap', post_id=post.id)) + + +@bp.route('/post//comment//edit', methods=['GET', 'POST']) +@login_required +def post_reply_edit(post_id: int, comment_id: int): + post = Post.query.get_or_404(post_id) + post_reply = PostReply.query.get_or_404(comment_id) + if post_reply.parent_id: + comment = PostReply.query.get_or_404(post_reply.parent_id) + else: + comment = None + form = NewReplyForm() + if post_reply.user_id == current_user.id or post.community.is_moderator(): + if form.validate_on_submit(): + post_reply.body = form.body.data + post_reply.body_html = markdown_to_html(form.body.data) + post_reply.notify_author = form.notify_author.data + post.community.last_active = utcnow() + post_reply.edited_at = utcnow() + db.session.commit() + post.flush_cache() + flash(_('Your changes have been saved.'), 'success') + # todo: federate edit + return redirect(url_for('activitypub.post_ap', post_id=post.id)) + else: + form.body.data = post_reply.body + form.notify_author.data = not post_reply.notify_author + return render_template('post/post_reply_edit.html', title=_('Edit comment'), form=form, post=post, post_reply=post_reply, + comment=comment, markdown_editor=True) + else: + abort(401) + + +@bp.route('/post//comment//delete', methods=['GET', 'POST']) +@login_required +def post_reply_delete(post_id: int, comment_id: int): + post = Post.query.get_or_404(post_id) + post_reply = PostReply.query.get_or_404(comment_id) + community = post.community + if post_reply.user_id == current_user.id or community.is_moderator(): + if post_reply.has_replies(): + post_reply.body = 'Deleted by author' if post_reply.author.id == current_user.id else 'Deleted by moderator' + post_reply.body_html = markdown_to_html(post_reply.body) + else: + post_reply.delete_dependencies() + db.session.delete(post_reply) + g.site.last_active = community.last_active = utcnow() + db.session.commit() + post.flush_cache() + flash(_('Comment deleted.')) + # todo: federate delete + return redirect(url_for('activitypub.post_ap', post_id=post.id)) \ No newline at end of file diff --git a/app/static/structure.css b/app/static/structure.css index e703bf4c..95643f63 100644 --- a/app/static/structure.css +++ b/app/static/structure.css @@ -618,6 +618,7 @@ fieldset legend { } .comment .comment_actions { margin-top: -10px; + position: relative; } .comment .comment_actions a { text-decoration: none; diff --git a/app/static/structure.scss b/app/static/structure.scss index b234c5b9..4ea51db6 100644 --- a/app/static/structure.scss +++ b/app/static/structure.scss @@ -337,6 +337,7 @@ nav, etc which are used site-wide */ .comment_actions { margin-top: -10px; + position: relative; a { text-decoration: none; } diff --git a/app/static/styles.css b/app/static/styles.css index 3147ce17..d9979570 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -545,6 +545,15 @@ nav.navbar { text-decoration: none; } +.comment_actions_link { + display: block; + position: absolute; + bottom: 0; + right: 48px; + width: 41px; + text-decoration: none; +} + .alert { width: 96%; } diff --git a/app/static/styles.scss b/app/static/styles.scss index 6eb2db88..4607a293 100644 --- a/app/static/styles.scss +++ b/app/static/styles.scss @@ -241,6 +241,15 @@ nav.navbar { text-decoration: none; } +.comment_actions_link { + display: block; + position: absolute; + bottom: 0; + right: 48px; + width: 41px; + text-decoration: none; +} + .alert { width: 96%; } diff --git a/app/templates/post/post.html b/app/templates/post/post.html index e8b511f1..91430eae 100644 --- a/app/templates/post/post.html +++ b/app/templates/post/post.html @@ -89,13 +89,14 @@ {{ comment['comment'].body_html | safe }} - {% if current_user.is_authenticated and current_user.verified %} -
+
+ {% if current_user.is_authenticated and current_user.verified %} {% if post.comments_enabled %} reply {% endif %} -
- {% endif %} + {% endif %} + +
{% if comment['replies'] %} {% if comment['comment'].depth <= THREAD_CUTOFF_DEPTH %}
diff --git a/app/templates/post/post_options.html b/app/templates/post/post_options.html index 78bbb99f..68bf4440 100644 --- a/app/templates/post/post_options.html +++ b/app/templates/post/post_options.html @@ -26,16 +26,16 @@
  • {{ _('Block domain %(domain)s', domain=post.domain.name) }}
  • {% endif %} - {% if post.instance_id %} + {% if post.instance_id and post.instance_id != 1 %}
  • - {{ _('Hide every post from %(name)s', name=post.instance.domain) }}
  • + {{ _("Hide every post from author's instance: %(name)s", name=post.instance.domain) }} {% endif %} {% endif %}
  • {{ _('Report to moderators') }}
  • - {% endif %} +

    {{ _('If you want to perform more than one of these (e.g. block and report), hold down Ctrl and click, then complete the operation in the new tabs that open.') }}

    diff --git a/app/templates/post/post_reply_edit.html b/app/templates/post/post_reply_edit.html new file mode 100644 index 00000000..b85ecb93 --- /dev/null +++ b/app/templates/post/post_reply_edit.html @@ -0,0 +1,85 @@ +{% extends "base.html" %} +{% from 'bootstrap/form.html' import render_form %} + +{% block app_content %} + +
    +
    +
    Original post +

    {{ post.title }}

    + {{ post.body_html | safe }} +
    + {% if comment %} +
    Comment you are replying to + {{ comment.body_html | safe}} +
    + {% endif %} +
    + {{ render_form(form) }} + {% if markdown_editor %} + + {% endif %} +
    +
    +
    +
    +
    +
    +
    + {% if current_user.is_authenticated and community_membership(current_user, post.community) %} + {{ _('Unsubscribe') }} + {% else %} + {{ _('Subscribe') }} + {% endif %} +
    + +
    +
    + +
    +
    +
    +
    +
    +

    {{ _('About community') }}

    +
    +
    +

    {{ post.community.description|safe }}

    +

    {{ post.community.rules|safe }}

    + {% if len(mods) > 0 and not post.community.private_mods %} +

    Moderators

    +
      + {% for mod in mods %} +
    1. {{ mod.user_name }}
    2. + {% endfor %} +
    + {% endif %} +
    +
    + {% if is_moderator %} +
    +
    +

    {{ _('Community Settings') }}

    +
    + +
    + {% endif %} +
    +
    + +{% endblock %} diff --git a/app/templates/post/post_reply_options.html b/app/templates/post/post_reply_options.html new file mode 100644 index 00000000..12c166fa --- /dev/null +++ b/app/templates/post/post_reply_options.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} +{% from 'bootstrap/form.html' import render_form %} + +{% block app_content %} +
    + +
    +{% endblock %} \ No newline at end of file diff --git a/app/templates/post/post_reply_report.html b/app/templates/post/post_reply_report.html new file mode 100644 index 00000000..0796521e --- /dev/null +++ b/app/templates/post/post_reply_report.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% from 'bootstrap/form.html' import render_form %} + +{% block app_content %} +
    + +
    +{% endblock %} \ No newline at end of file diff --git a/migrations/versions/e8113bc01e3a_post_reply_instance.py b/migrations/versions/e8113bc01e3a_post_reply_instance.py new file mode 100644 index 00000000..255574cb --- /dev/null +++ b/migrations/versions/e8113bc01e3a_post_reply_instance.py @@ -0,0 +1,36 @@ +"""post reply instance + +Revision ID: e8113bc01e3a +Revises: 52789c4b1d0f +Create Date: 2023-12-28 13:11:04.462308 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e8113bc01e3a' +down_revision = '52789c4b1d0f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('post_reply', schema=None) as batch_op: + batch_op.add_column(sa.Column('instance_id', sa.Integer(), nullable=True)) + batch_op.create_index(batch_op.f('ix_post_reply_instance_id'), ['instance_id'], unique=False) + batch_op.create_foreign_key(None, 'instance', ['instance_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('post_reply', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_index(batch_op.f('ix_post_reply_instance_id')) + batch_op.drop_column('instance_id') + + # ### end Alembic commands ### diff --git a/regional.txt b/regional.txt new file mode 100644 index 00000000..4c5d011d --- /dev/null +++ b/regional.txt @@ -0,0 +1,2 @@ +africa + https://baraza.africa/c/africa