mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36: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()
|
post.delete_dependencies()
|
||||||
announce_activity_to_followers(post.community, post.author, request_json)
|
announce_activity_to_followers(post.community, post.author, request_json)
|
||||||
post.deleted = True
|
post.deleted = True
|
||||||
|
post.author.post_count -= 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
activity_log.result = 'success'
|
activity_log.result = 'success'
|
||||||
else:
|
else:
|
||||||
|
@ -1119,6 +1120,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
|
||||||
reply.post.reply_count -= 1
|
reply.post.reply_count -= 1
|
||||||
reply.deleted = True
|
reply.deleted = True
|
||||||
announce_activity_to_followers(reply.community, reply.author, request_json)
|
announce_activity_to_followers(reply.community, reply.author, request_json)
|
||||||
|
reply.author.post_reply_count -= 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
activity_log.result = 'success'
|
activity_log.result = 'success'
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -543,6 +543,7 @@ def refresh_user_profile_task(user_id):
|
||||||
user.cover = cover
|
user.cover = cover
|
||||||
db.session.add(cover)
|
db.session.add(cover)
|
||||||
cover_changed = True
|
cover_changed = True
|
||||||
|
user.recalculate_post_stats()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if user.avatar_id and avatar_changed:
|
if user.avatar_id and avatar_changed:
|
||||||
make_image_sizes(user.avatar_id, 40, 250, 'users')
|
make_image_sizes(user.avatar_id, 40, 250, 'users')
|
||||||
|
@ -1310,11 +1311,14 @@ def downvote_post(post, user):
|
||||||
post.down_votes += 1
|
post.down_votes += 1
|
||||||
# Make 'hot' sort more spicy by amplifying the effect of early downvotes
|
# Make 'hot' sort more spicy by amplifying the effect of early downvotes
|
||||||
if post.up_votes + post.down_votes <= 30:
|
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:
|
elif post.up_votes + post.down_votes <= 60:
|
||||||
post.score -= current_app.config['SPICY_UNDER_60']
|
effect = current_app.config['SPICY_UNDER_60']
|
||||||
else:
|
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,
|
vote = PostVote(user_id=user.id, post_id=post.id, author_id=post.author.id,
|
||||||
effect=effect)
|
effect=effect)
|
||||||
post.author.reputation += effect
|
post.author.reputation += effect
|
||||||
|
@ -1349,7 +1353,9 @@ def downvote_post_reply(comment, user):
|
||||||
if not existing_vote:
|
if not existing_vote:
|
||||||
effect = -1.0
|
effect = -1.0
|
||||||
comment.down_votes += 1
|
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,
|
vote = PostReplyVote(user_id=user.id, post_reply_id=comment.id,
|
||||||
author_id=comment.author.id, effect=effect)
|
author_id=comment.author.id, effect=effect)
|
||||||
comment.author.reputation += effect
|
comment.author.reputation += effect
|
||||||
|
@ -1381,6 +1387,8 @@ def upvote_post_reply(comment, user):
|
||||||
effect = instance_weight(user.ap_domain)
|
effect = instance_weight(user.ap_domain)
|
||||||
existing_vote = PostReplyVote.query.filter_by(user_id=user.id,
|
existing_vote = PostReplyVote.query.filter_by(user_id=user.id,
|
||||||
post_reply_id=comment.id).first()
|
post_reply_id=comment.id).first()
|
||||||
|
if user.cannot_vote():
|
||||||
|
effect = 0
|
||||||
if not existing_vote:
|
if not existing_vote:
|
||||||
comment.up_votes += 1
|
comment.up_votes += 1
|
||||||
comment.score += effect
|
comment.score += effect
|
||||||
|
@ -1424,6 +1432,8 @@ def upvote_post(post, user):
|
||||||
spicy_effect = effect * current_app.config['SPICY_UNDER_30']
|
spicy_effect = effect * current_app.config['SPICY_UNDER_30']
|
||||||
elif post.up_votes + post.down_votes <= 60:
|
elif post.up_votes + post.down_votes <= 60:
|
||||||
spicy_effect = effect * current_app.config['SPICY_UNDER_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()
|
existing_vote = PostVote.query.filter_by(user_id=user.id, post_id=post.id).first()
|
||||||
if not existing_vote:
|
if not existing_vote:
|
||||||
post.up_votes += 1
|
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.delete_dependencies()
|
||||||
to_delete.deleted = True
|
to_delete.deleted = True
|
||||||
community.post_count -= 1
|
community.post_count -= 1
|
||||||
|
to_delete.author.post_count -= 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if to_delete.author.id != deletor.id:
|
if to_delete.author.id != deletor.id:
|
||||||
add_to_modlog_activitypub('delete_post', deletor, community_id=community.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:
|
else:
|
||||||
to_delete.delete_dependencies()
|
to_delete.delete_dependencies()
|
||||||
to_delete.deleted = True
|
to_delete.deleted = True
|
||||||
|
to_delete.author.post_reply_count -= 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if to_delete.author.id != deletor.id:
|
if to_delete.author.id != deletor.id:
|
||||||
add_to_modlog_activitypub('delete_post_reply', deletor, community_id=community.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()
|
# TODO: restore_dependencies()
|
||||||
to_restore.deleted = False
|
to_restore.deleted = False
|
||||||
community.post_count += 1
|
community.post_count += 1
|
||||||
|
to_restore.author.post_count += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if to_restore.author.id != restorer.id:
|
if to_restore.author.id != restorer.id:
|
||||||
add_to_modlog_activitypub('restore_post', restorer, community_id=community.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()
|
community.last_active = post.last_active = utcnow()
|
||||||
activity_log.result = 'success'
|
activity_log.result = 'success'
|
||||||
post_reply.ranking = confidence(post_reply.up_votes, post_reply.down_votes)
|
post_reply.ranking = confidence(post_reply.up_votes, post_reply.down_votes)
|
||||||
|
user.post_reply_count += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# send notification to the post/comment being replied to
|
# 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.post_count += 1
|
||||||
community.last_active = utcnow()
|
community.last_active = utcnow()
|
||||||
activity_log.result = 'success'
|
activity_log.result = 'success'
|
||||||
|
user.post_count += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Polls need to be processed quite late because they need a post_id to refer to
|
# 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)
|
post = Post(user_id=current_user.id, community_id=form.communities.data, instance_id=1)
|
||||||
save_post(form, post, post_type)
|
save_post(form, post, post_type)
|
||||||
community.post_count += 1
|
community.post_count += 1
|
||||||
|
current_user.post_count += 1
|
||||||
community.last_active = g.site.last_active = utcnow()
|
community.last_active = g.site.last_active = utcnow()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"
|
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))
|
timezone = db.Column(db.String(20))
|
||||||
reputation = db.Column(db.Float, default=0.0)
|
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
|
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_customer_id = db.Column(db.String(50))
|
||||||
stripe_subscription_id = db.Column(db.String(50))
|
stripe_subscription_id = db.Column(db.String(50))
|
||||||
searchable = db.Column(db.Boolean, default=True)
|
searchable = db.Column(db.Boolean, default=True)
|
||||||
|
@ -805,6 +807,11 @@ class User(UserMixin, db.Model):
|
||||||
return False
|
return False
|
||||||
return True
|
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:
|
def link(self) -> str:
|
||||||
if self.is_local():
|
if self.is_local():
|
||||||
return self.user_name
|
return self.user_name
|
||||||
|
@ -843,24 +850,21 @@ class User(UserMixin, db.Model):
|
||||||
return self.expires < datetime(2019, 9, 1)
|
return self.expires < datetime(2019, 9, 1)
|
||||||
|
|
||||||
def recalculate_attitude(self):
|
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'),
|
upvotes = downvotes = 0
|
||||||
{'user_id': self.id}).scalar()
|
last_50_votes = PostVote.query.filter(PostVote.user_id == self.id).order_by(-PostVote.id).limit(50)
|
||||||
downvotes = db.session.execute(text('SELECT COUNT(id) as c FROM "post_vote" WHERE user_id = :user_id AND effect < 0'),
|
for vote in last_50_votes:
|
||||||
{'user_id': self.id}).scalar()
|
if vote.effect > 0:
|
||||||
if upvotes is None:
|
upvotes += 1
|
||||||
upvotes = 0
|
if vote.effect < 0:
|
||||||
if downvotes is None:
|
downvotes += 1
|
||||||
downvotes = 0
|
|
||||||
|
|
||||||
comment_upvotes = db.session.execute(text('SELECT COUNT(id) as c FROM "post_reply_vote" WHERE user_id = :user_id AND effect > 0'),
|
comment_upvotes = comment_downvotes = 0
|
||||||
{'user_id': self.id}).scalar()
|
last_50_votes = PostReplyVote.query.filter(PostReplyVote.user_id == self.id).order_by(-PostReplyVote.id).limit(50)
|
||||||
comment_downvotes = db.session.execute(text('SELECT COUNT(id) as c FROM "post_reply_vote" WHERE user_id = :user_id AND effect < 0'),
|
for vote in last_50_votes:
|
||||||
{'user_id': self.id}).scalar()
|
if vote.effect > 0:
|
||||||
|
comment_upvotes += 1
|
||||||
if comment_upvotes is None:
|
if vote.effect < 0:
|
||||||
comment_upvotes = 0
|
comment_downvotes += 1
|
||||||
if comment_downvotes is None:
|
|
||||||
comment_downvotes = 0
|
|
||||||
|
|
||||||
total_upvotes = upvotes + comment_upvotes
|
total_upvotes = upvotes + comment_upvotes
|
||||||
total_downvotes = downvotes + comment_downvotes
|
total_downvotes = downvotes + comment_downvotes
|
||||||
|
@ -873,6 +877,14 @@ class User(UserMixin, db.Model):
|
||||||
else:
|
else:
|
||||||
self.attitude = 1.0
|
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:
|
def subscribed(self, community_id: int) -> int:
|
||||||
if community_id is None:
|
if community_id is None:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -15,7 +15,7 @@ from app.community.util import save_post, send_to_remote_instance
|
||||||
from app.inoculation import inoculation
|
from app.inoculation import inoculation
|
||||||
from app.post.forms import NewReplyForm, ReportPostForm, MeaCulpaForm
|
from app.post.forms import NewReplyForm, ReportPostForm, MeaCulpaForm
|
||||||
from app.community.forms import CreateLinkForm, CreateImageForm, CreateDiscussionForm, CreateVideoForm, CreatePollForm, EditImageForm
|
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
|
generate_archive_link, body_has_no_archive_link
|
||||||
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_LINK, \
|
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_LINK, \
|
||||||
POST_TYPE_IMAGE, \
|
POST_TYPE_IMAGE, \
|
||||||
|
@ -111,6 +111,7 @@ def show_post(post_id: int):
|
||||||
post.last_active = community.last_active = utcnow()
|
post.last_active = community.last_active = utcnow()
|
||||||
post.reply_count += 1
|
post.reply_count += 1
|
||||||
community.post_reply_count += 1
|
community.post_reply_count += 1
|
||||||
|
current_user.post_reply_count += 1
|
||||||
current_user.language_id = form.language_id.data
|
current_user.language_id = form.language_id.data
|
||||||
|
|
||||||
db.session.add(reply)
|
db.session.add(reply)
|
||||||
|
@ -737,8 +738,9 @@ def add_reply(post_id: int, comment_id: int):
|
||||||
elif current_user.reputation < -100:
|
elif current_user.reputation < -100:
|
||||||
reply.score -= 1
|
reply.score -= 1
|
||||||
reply.ranking -= 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()
|
post.last_active = post.community.last_active = utcnow()
|
||||||
|
current_user.post_reply_count += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
form.body.data = ''
|
form.body.data = ''
|
||||||
flash('Your comment has been added.')
|
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)
|
ocp.cross_posts.remove(post.id)
|
||||||
post.delete_dependencies()
|
post.delete_dependencies()
|
||||||
post.deleted = True
|
post.deleted = True
|
||||||
|
post.author.post_count -= 1
|
||||||
|
community.post_count -= 1
|
||||||
if hasattr(g, 'site'): # g.site is invalid when running from cli
|
if hasattr(g, 'site'): # g.site is invalid when running from cli
|
||||||
g.site.last_active = community.last_active = utcnow()
|
g.site.last_active = community.last_active = utcnow()
|
||||||
flash(_('Post deleted.'))
|
flash(_('Post deleted.'))
|
||||||
|
@ -1266,6 +1270,8 @@ def post_restore(post_id: int):
|
||||||
post = Post.query.get_or_404(post_id)
|
post = Post.query.get_or_404(post_id)
|
||||||
if post.community.is_moderator() or post.community.is_owner() or current_user.is_admin():
|
if post.community.is_moderator() or post.community.is_owner() or current_user.is_admin():
|
||||||
post.deleted = False
|
post.deleted = False
|
||||||
|
post.author.post_count += 1
|
||||||
|
post.community.post_count += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Federate un-delete
|
# Federate un-delete
|
||||||
|
@ -1781,6 +1787,7 @@ def post_reply_delete(post_id: int, comment_id: int):
|
||||||
post_reply.delete_dependencies()
|
post_reply.delete_dependencies()
|
||||||
post_reply.deleted = True
|
post_reply.deleted = True
|
||||||
g.site.last_active = community.last_active = utcnow()
|
g.site.last_active = community.last_active = utcnow()
|
||||||
|
post_reply.author.post_reply_count -= 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(_('Comment deleted.'))
|
flash(_('Comment deleted.'))
|
||||||
# federate delete
|
# 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
|
# 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'),
|
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()
|
{'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…
Add table
Reference in a new issue