mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
track user statistics like post count
This commit is contained in:
parent
1a658d007f
commit
51f2b3e40e
7 changed files with 116 additions and 24 deletions
|
@ -1104,6 +1104,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
|
|||
post.delete_dependencies()
|
||||
announce_activity_to_followers(post.community, post.author, request_json)
|
||||
post.deleted = True
|
||||
post.author.post_count -= 1
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
|
@ -1119,6 +1120,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
|
|||
reply.post.reply_count -= 1
|
||||
reply.deleted = True
|
||||
announce_activity_to_followers(reply.community, reply.author, request_json)
|
||||
reply.author.post_reply_count -= 1
|
||||
db.session.commit()
|
||||
activity_log.result = 'success'
|
||||
else:
|
||||
|
|
|
@ -543,6 +543,7 @@ def refresh_user_profile_task(user_id):
|
|||
user.cover = cover
|
||||
db.session.add(cover)
|
||||
cover_changed = True
|
||||
user.recalculate_post_stats()
|
||||
db.session.commit()
|
||||
if user.avatar_id and avatar_changed:
|
||||
make_image_sizes(user.avatar_id, 40, 250, 'users')
|
||||
|
@ -1310,11 +1311,14 @@ def downvote_post(post, user):
|
|||
post.down_votes += 1
|
||||
# Make 'hot' sort more spicy by amplifying the effect of early downvotes
|
||||
if post.up_votes + post.down_votes <= 30:
|
||||
post.score -= current_app.config['SPICY_UNDER_30']
|
||||
effect = current_app.config['SPICY_UNDER_30']
|
||||
elif post.up_votes + post.down_votes <= 60:
|
||||
post.score -= current_app.config['SPICY_UNDER_60']
|
||||
effect = current_app.config['SPICY_UNDER_60']
|
||||
else:
|
||||
post.score -= 1.0
|
||||
effect = -1.0
|
||||
if user.cannot_vote():
|
||||
effect = 0
|
||||
post.score -= effect
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
post.author.reputation += effect
|
||||
|
@ -1349,7 +1353,9 @@ def downvote_post_reply(comment, user):
|
|||
if not existing_vote:
|
||||
effect = -1.0
|
||||
comment.down_votes += 1
|
||||
comment.score -= 1.0
|
||||
if user.cannot_vote():
|
||||
effect = 0
|
||||
comment.score -= effect
|
||||
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||
author_id=comment.author.id, effect=effect)
|
||||
comment.author.reputation += effect
|
||||
|
@ -1381,6 +1387,8 @@ def upvote_post_reply(comment, user):
|
|||
effect = instance_weight(user.ap_domain)
|
||||
existing_vote = PostReplyVote.query.filter_by(user_id=user.id,
|
||||
post_reply_id=comment.id).first()
|
||||
if user.cannot_vote():
|
||||
effect = 0
|
||||
if not existing_vote:
|
||||
comment.up_votes += 1
|
||||
comment.score += effect
|
||||
|
@ -1424,6 +1432,8 @@ def upvote_post(post, user):
|
|||
spicy_effect = effect * current_app.config['SPICY_UNDER_30']
|
||||
elif post.up_votes + post.down_votes <= 60:
|
||||
spicy_effect = effect * current_app.config['SPICY_UNDER_60']
|
||||
if user.cannot_vote():
|
||||
effect = spicy_effect = 0
|
||||
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=post.id).first()
|
||||
if not existing_vote:
|
||||
post.up_votes += 1
|
||||
|
@ -1474,6 +1484,7 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id
|
|||
to_delete.delete_dependencies()
|
||||
to_delete.deleted = True
|
||||
community.post_count -= 1
|
||||
to_delete.author.post_count -= 1
|
||||
db.session.commit()
|
||||
if to_delete.author.id != deletor.id:
|
||||
add_to_modlog_activitypub('delete_post', deletor, community_id=community.id,
|
||||
|
@ -1487,6 +1498,7 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id
|
|||
else:
|
||||
to_delete.delete_dependencies()
|
||||
to_delete.deleted = True
|
||||
to_delete.author.post_reply_count -= 1
|
||||
db.session.commit()
|
||||
if to_delete.author.id != deletor.id:
|
||||
add_to_modlog_activitypub('delete_post_reply', deletor, community_id=community.id,
|
||||
|
@ -1513,6 +1525,7 @@ def restore_post_or_comment_task(object_json):
|
|||
# TODO: restore_dependencies()
|
||||
to_restore.deleted = False
|
||||
community.post_count += 1
|
||||
to_restore.author.post_count += 1
|
||||
db.session.commit()
|
||||
if to_restore.author.id != restorer.id:
|
||||
add_to_modlog_activitypub('restore_post', restorer, community_id=community.id,
|
||||
|
@ -1816,6 +1829,7 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
|
|||
community.last_active = post.last_active = utcnow()
|
||||
activity_log.result = 'success'
|
||||
post_reply.ranking = confidence(post_reply.up_votes, post_reply.down_votes)
|
||||
user.post_reply_count += 1
|
||||
db.session.commit()
|
||||
|
||||
# send notification to the post/comment being replied to
|
||||
|
@ -2014,6 +2028,7 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
|||
community.post_count += 1
|
||||
community.last_active = utcnow()
|
||||
activity_log.result = 'success'
|
||||
user.post_count += 1
|
||||
db.session.commit()
|
||||
|
||||
# Polls need to be processed quite late because they need a post_id to refer to
|
||||
|
|
|
@ -584,6 +584,7 @@ def add_post(actor, type):
|
|||
post = Post(user_id=current_user.id, community_id=form.communities.data, instance_id=1)
|
||||
save_post(form, post, post_type)
|
||||
community.post_count += 1
|
||||
current_user.post_count += 1
|
||||
community.last_active = g.site.last_active = utcnow()
|
||||
db.session.commit()
|
||||
post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"
|
||||
|
|
|
@ -637,6 +637,8 @@ class User(UserMixin, db.Model):
|
|||
timezone = db.Column(db.String(20))
|
||||
reputation = db.Column(db.Float, default=0.0)
|
||||
attitude = db.Column(db.Float, default=1.0) # (upvotes cast - downvotes cast) / (upvotes + downvotes). A number between 1 and -1 is the ratio between up and down votes they cast
|
||||
post_count = db.Column(db.Integer, default=0)
|
||||
post_reply_count = db.Column(db.Integer, default=0)
|
||||
stripe_customer_id = db.Column(db.String(50))
|
||||
stripe_subscription_id = db.Column(db.String(50))
|
||||
searchable = db.Column(db.Boolean, default=True)
|
||||
|
@ -805,6 +807,11 @@ class User(UserMixin, db.Model):
|
|||
return False
|
||||
return True
|
||||
|
||||
def cannot_vote(self):
|
||||
if self.is_local():
|
||||
return False
|
||||
return self.post_count == 0 and self.post_reply_count == 0 and len(self.user_name) == 8 # most vote manipulation bots have 8 character user names and never post any content
|
||||
|
||||
def link(self) -> str:
|
||||
if self.is_local():
|
||||
return self.user_name
|
||||
|
@ -843,24 +850,21 @@ class User(UserMixin, db.Model):
|
|||
return self.expires < datetime(2019, 9, 1)
|
||||
|
||||
def recalculate_attitude(self):
|
||||
upvotes = db.session.execute(text('SELECT COUNT(id) as c FROM "post_vote" WHERE user_id = :user_id AND effect > 0'),
|
||||
{'user_id': self.id}).scalar()
|
||||
downvotes = db.session.execute(text('SELECT COUNT(id) as c FROM "post_vote" WHERE user_id = :user_id AND effect < 0'),
|
||||
{'user_id': self.id}).scalar()
|
||||
if upvotes is None:
|
||||
upvotes = 0
|
||||
if downvotes is None:
|
||||
downvotes = 0
|
||||
upvotes = downvotes = 0
|
||||
last_50_votes = PostVote.query.filter(PostVote.user_id == self.id).order_by(-PostVote.id).limit(50)
|
||||
for vote in last_50_votes:
|
||||
if vote.effect > 0:
|
||||
upvotes += 1
|
||||
if vote.effect < 0:
|
||||
downvotes += 1
|
||||
|
||||
comment_upvotes = db.session.execute(text('SELECT COUNT(id) as c FROM "post_reply_vote" WHERE user_id = :user_id AND effect > 0'),
|
||||
{'user_id': self.id}).scalar()
|
||||
comment_downvotes = db.session.execute(text('SELECT COUNT(id) as c FROM "post_reply_vote" WHERE user_id = :user_id AND effect < 0'),
|
||||
{'user_id': self.id}).scalar()
|
||||
|
||||
if comment_upvotes is None:
|
||||
comment_upvotes = 0
|
||||
if comment_downvotes is None:
|
||||
comment_downvotes = 0
|
||||
comment_upvotes = comment_downvotes = 0
|
||||
last_50_votes = PostReplyVote.query.filter(PostReplyVote.user_id == self.id).order_by(-PostReplyVote.id).limit(50)
|
||||
for vote in last_50_votes:
|
||||
if vote.effect > 0:
|
||||
comment_upvotes += 1
|
||||
if vote.effect < 0:
|
||||
comment_downvotes += 1
|
||||
|
||||
total_upvotes = upvotes + comment_upvotes
|
||||
total_downvotes = downvotes + comment_downvotes
|
||||
|
@ -873,6 +877,14 @@ class User(UserMixin, db.Model):
|
|||
else:
|
||||
self.attitude = 1.0
|
||||
|
||||
def recalculate_post_stats(self, posts=True, replies=True):
|
||||
if posts:
|
||||
self.post_count = db.session.execute(text('SELECT COUNT(id) as c FROM "post" WHERE user_id = :user_id AND deleted = false'),
|
||||
{'user_id': self.id}).scalar()
|
||||
if replies:
|
||||
self.post_reply_count = db.session.execute(text('SELECT COUNT(id) as c FROM "post_reply" WHERE user_id = :user_id AND deleted = false'),
|
||||
{'user_id': self.id}).scalar()
|
||||
|
||||
def subscribed(self, community_id: int) -> int:
|
||||
if community_id is None:
|
||||
return False
|
||||
|
|
|
@ -15,7 +15,7 @@ from app.community.util import save_post, send_to_remote_instance
|
|||
from app.inoculation import inoculation
|
||||
from app.post.forms import NewReplyForm, ReportPostForm, MeaCulpaForm
|
||||
from app.community.forms import CreateLinkForm, CreateImageForm, CreateDiscussionForm, CreateVideoForm, CreatePollForm, EditImageForm
|
||||
from app.post.util import post_replies, get_comment_branch, post_reply_count, tags_to_string, url_needs_archive, \
|
||||
from app.post.util import post_replies, get_comment_branch, get_post_reply_count, tags_to_string, url_needs_archive, \
|
||||
generate_archive_link, body_has_no_archive_link
|
||||
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_LINK, \
|
||||
POST_TYPE_IMAGE, \
|
||||
|
@ -111,6 +111,7 @@ def show_post(post_id: int):
|
|||
post.last_active = community.last_active = utcnow()
|
||||
post.reply_count += 1
|
||||
community.post_reply_count += 1
|
||||
current_user.post_reply_count += 1
|
||||
current_user.language_id = form.language_id.data
|
||||
|
||||
db.session.add(reply)
|
||||
|
@ -737,8 +738,9 @@ def add_reply(post_id: int, comment_id: int):
|
|||
elif current_user.reputation < -100:
|
||||
reply.score -= 1
|
||||
reply.ranking -= 1
|
||||
post.reply_count = post_reply_count(post.id)
|
||||
post.reply_count = get_post_reply_count(post.id)
|
||||
post.last_active = post.community.last_active = utcnow()
|
||||
current_user.post_reply_count += 1
|
||||
db.session.commit()
|
||||
form.body.data = ''
|
||||
flash('Your comment has been added.')
|
||||
|
@ -1201,6 +1203,8 @@ def post_delete_post(community: Community, post: Post, user_id: int, federate_al
|
|||
ocp.cross_posts.remove(post.id)
|
||||
post.delete_dependencies()
|
||||
post.deleted = True
|
||||
post.author.post_count -= 1
|
||||
community.post_count -= 1
|
||||
if hasattr(g, 'site'): # g.site is invalid when running from cli
|
||||
g.site.last_active = community.last_active = utcnow()
|
||||
flash(_('Post deleted.'))
|
||||
|
@ -1266,6 +1270,8 @@ def post_restore(post_id: int):
|
|||
post = Post.query.get_or_404(post_id)
|
||||
if post.community.is_moderator() or post.community.is_owner() or current_user.is_admin():
|
||||
post.deleted = False
|
||||
post.author.post_count += 1
|
||||
post.community.post_count += 1
|
||||
db.session.commit()
|
||||
|
||||
# Federate un-delete
|
||||
|
@ -1781,6 +1787,7 @@ def post_reply_delete(post_id: int, comment_id: int):
|
|||
post_reply.delete_dependencies()
|
||||
post_reply.deleted = True
|
||||
g.site.last_active = community.last_active = utcnow()
|
||||
post_reply.author.post_reply_count -= 1
|
||||
db.session.commit()
|
||||
flash(_('Comment deleted.'))
|
||||
# federate delete
|
||||
|
|
|
@ -76,7 +76,7 @@ def get_comment_branch(post_id: int, comment_id: int, sort_by: str) -> List[Post
|
|||
|
||||
|
||||
# The number of replies a post has
|
||||
def post_reply_count(post_id) -> int:
|
||||
def get_post_reply_count(post_id) -> int:
|
||||
return db.session.execute(text('SELECT COUNT(id) as c FROM "post_reply" WHERE post_id = :post_id AND deleted is false'),
|
||||
{'post_id': post_id}).scalar()
|
||||
|
||||
|
|
55
migrations/versions/fdaeb0b2c078_user_stats.py
Normal file
55
migrations/versions/fdaeb0b2c078_user_stats.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""user stats
|
||||
|
||||
Revision ID: fdaeb0b2c078
|
||||
Revises: 2cae414cbc7a
|
||||
Create Date: 2024-09-13 09:37:24.847306
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'fdaeb0b2c078'
|
||||
down_revision = '2cae414cbc7a'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('post_count', sa.Integer(), nullable=True))
|
||||
batch_op.add_column(sa.Column('post_reply_count', sa.Integer(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
op.execute(sa.DDL('UPDATE "user" SET post_count = 0, post_reply_count = 0'))
|
||||
|
||||
op.execute(sa.DDL("""UPDATE "user"
|
||||
SET post_count = subquery.post_count
|
||||
FROM (
|
||||
SELECT user_id, COUNT(*) as post_count
|
||||
FROM post
|
||||
WHERE deleted = false
|
||||
GROUP BY user_id
|
||||
) AS subquery
|
||||
WHERE "user".id = subquery.user_id;"""))
|
||||
|
||||
op.execute(sa.DDL("""UPDATE "user"
|
||||
SET post_reply_count = subquery.post_count
|
||||
FROM (
|
||||
SELECT user_id, COUNT(*) as post_count
|
||||
FROM post_reply
|
||||
WHERE deleted = false
|
||||
GROUP BY user_id
|
||||
) AS subquery
|
||||
WHERE "user".id = subquery.user_id;"""))
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.drop_column('post_reply_count')
|
||||
batch_op.drop_column('post_count')
|
||||
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in a new issue