federate votes

This commit is contained in:
rimu 2023-09-10 20:20:53 +12:00
parent bfc4b243bf
commit 08a771daf0
4 changed files with 139 additions and 4 deletions

View file

@ -7,10 +7,11 @@ from flask import request, Response, render_template, current_app, abort, jsonif
from app.activitypub.signature import HttpSignature
from app.community.routes import show_community
from app.models import User, Community, CommunityJoinRequest, CommunityMember, CommunityBan, ActivityPubLog
from app.models import User, Community, CommunityJoinRequest, CommunityMember, CommunityBan, ActivityPubLog, Post, \
PostReply, Instance, PostVote, PostReplyVote
from app.activitypub.util import public_key, users_total, active_half_year, active_month, local_posts, local_comments, \
post_to_activity, find_actor_or_create
from app.utils import gibberish
from app.utils import gibberish, get_setting
INBOX = []
@ -222,7 +223,48 @@ def shared_inbox():
if 'type' in request_json:
activity_log.activity_type = request_json['type']
if request_json['type'] == 'Announce':
...
if request_json['object']['type'] == 'Like' or request_json['object']['type'] == 'Dislike':
activity_log.activity_type = request_json['object']['type']
vote_effect = 1.0 if request_json['object']['type'] == 'Like' else -1.0
if vote_effect < 0 and get_setting('allow_dislike', True) is False:
activity_log.exception_message = 'Dislike ignored because of allow_dislike setting'
else:
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 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 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.result='failure'
activity_log.exception_message = 'Could not detect type of like'
if activity_log.result == 'success':
... # todo: recalculate 'hotness' of liked post/reply
# remote user wants to follow one of our communities
elif request_json['type'] == 'Follow':
user_ap_id = request_json['actor']

View file

@ -49,6 +49,7 @@ def register(app):
db.configure_mappers()
db.create_all()
db.session.append(Settings(name='allow_nsfw', value=json.dumps(False)))
db.session.append(Settings(name='allow_dislike', value=json.dumps(True)))
db.session.append(BannedInstances(domain='lemmygrad.ml'))
db.session.append(BannedInstances(domain='gab.com'))
db.session.append(BannedInstances(domain='exploding-heads.com'))

View file

@ -117,6 +117,7 @@ class User(UserMixin, db.Model):
newsletter = db.Column(db.Boolean, default=True)
bounces = db.Column(db.SmallInteger, default=0)
timezone = db.Column(db.String(20))
reputation = db.Column(db.Float, default=0.0)
stripe_customer_id = db.Column(db.String(50))
stripe_subscription_id = db.Column(db.String(50))
searchable = db.Column(db.Boolean, default=True)
@ -349,6 +350,7 @@ class Instance(db.Model):
inbox = db.Column(db.String(256))
shared_inbox = db.Column(db.String(256))
outbox = db.Column(db.String(256))
vote_weight = db.Column(db.Float, default=1.0)
class Settings(db.Model):
@ -374,11 +376,29 @@ class UserFollowRequest(db.Model):
follow_id = db.Column(db.Integer, db.ForeignKey('user.id'))
class PostVote(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
effect = db.Column(db.Float)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class PostReplyVote(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
post_reply_id = db.Column(db.Integer, db.ForeignKey('post_reply.id'))
effect = db.Column(db.Float)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# save every activity to a log, to aid debugging
class ActivityPubLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
direction = db.Column(db.String(3)) # 'in' or 'out'
activity_id = db.Column(db.String(100), indexed=True)
activity_id = db.Column(db.String(100), index=True)
activity_type = db.Column(db.String(50)) # e.g. 'Follow', 'Accept', 'Like', etc
activity_json = db.Column(db.Text) # the full json of the activity
result = db.Column(db.String(10)) # 'success' or 'failure'

View file

@ -0,0 +1,72 @@
"""voting
Revision ID: 6b84580a94cd
Revises: f032dbdfbd1d
Create Date: 2023-09-10 19:59:15.735823
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6b84580a94cd'
down_revision = 'f032dbdfbd1d'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('post_vote',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('author_id', sa.Integer(), nullable=True),
sa.Column('post_id', sa.Integer(), nullable=True),
sa.Column('effect', sa.Float(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['author_id'], ['user.id'], ),
sa.ForeignKeyConstraint(['post_id'], ['post.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('post_reply_vote',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('author_id', sa.Integer(), nullable=True),
sa.Column('post_reply_id', sa.Integer(), nullable=True),
sa.Column('effect', sa.Float(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['author_id'], ['user.id'], ),
sa.ForeignKeyConstraint(['post_reply_id'], ['post_reply.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
with op.batch_alter_table('activity_pub_log', schema=None) as batch_op:
batch_op.add_column(sa.Column('activity_id', sa.String(length=100), nullable=True))
batch_op.create_index(batch_op.f('ix_activity_pub_log_activity_id'), ['activity_id'], unique=False)
with op.batch_alter_table('instance', schema=None) as batch_op:
batch_op.add_column(sa.Column('vote_weight', sa.Float(), nullable=True))
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('reputation', sa.Float(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_column('reputation')
with op.batch_alter_table('instance', schema=None) as batch_op:
batch_op.drop_column('vote_weight')
with op.batch_alter_table('activity_pub_log', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_activity_pub_log_activity_id'))
batch_op.drop_column('activity_id')
op.drop_table('post_reply_vote')
op.drop_table('post_vote')
# ### end Alembic commands ###