diff --git a/boofilsic/settings.py b/boofilsic/settings.py index f0d2cdfe..e02fe830 100644 --- a/boofilsic/settings.py +++ b/boofilsic/settings.py @@ -86,6 +86,7 @@ # Slack API token, for sending exceptions to Slack, may deprecate in future SLACK_API_TOKEN=(str, ""), NEODB_SENTRY_DSN=(str, ""), + NEODB_FANOUT_LIMIT_DAYS=(int, 9), ) # ====== End of user configuration variables ====== @@ -223,7 +224,7 @@ DOWNLOADER_RETRIES = env("NEODB_DOWNLOADER_RETRIES") DISABLE_CRON = env("NEODB_DISABLE_CRON") - +FANOUT_LIMIT_DAYS = env("NEODB_FANOUT_LIMIT_DAYS") # ====== USER CONFIGUTRATION END ====== DATABASE_ROUTERS = ["takahe.db_routes.TakaheRouter"] diff --git a/journal/migrations/0020_shelflogentry_unique_shelf_log_entry.py b/journal/migrations/0020_shelflogentry_unique_shelf_log_entry.py new file mode 100644 index 00000000..ecd7ab99 --- /dev/null +++ b/journal/migrations/0020_shelflogentry_unique_shelf_log_entry.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.7 on 2023-11-23 03:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("journal", "0019_alter_collection_edited_time_and_more"), + ] + + operations = [ + migrations.AddConstraint( + model_name="shelflogentry", + constraint=models.UniqueConstraint( + fields=("owner", "item", "timestamp", "shelf_type"), + name="unique_shelf_log_entry", + ), + ), + ] diff --git a/journal/models/shelf.py b/journal/models/shelf.py index fe8f596a..b483c732 100644 --- a/journal/models/shelf.py +++ b/journal/models/shelf.py @@ -296,8 +296,8 @@ def get_calendar_data(self, max_visiblity: int): [timezone_offset, shelf_id, int(max_visiblity)], ), ( - "SELECT to_char(DATE(journal_comment.created_time::timestamp AT TIME ZONE %s), 'YYYY-MM-DD') AS dat, django_content_type.model typ, COUNT(1) count FROM journal_comment, catalog_item, django_content_type WHERE journal_comment.item_id = catalog_item.id AND django_content_type.id = catalog_item.polymorphic_ctype_id AND journal_comment.created_time >= NOW() - INTERVAL '366 days' AND journal_comment.visibility <= %s GROUP BY item_id, dat, typ;", - [timezone_offset, int(max_visiblity)], + "SELECT to_char(DATE(journal_comment.created_time::timestamp AT TIME ZONE %s), 'YYYY-MM-DD') AS dat, django_content_type.model typ, COUNT(1) count FROM journal_comment, catalog_item, django_content_type WHERE journal_comment.owner_id = %s AND journal_comment.item_id = catalog_item.id AND django_content_type.id = catalog_item.polymorphic_ctype_id AND journal_comment.created_time >= NOW() - INTERVAL '366 days' AND journal_comment.visibility <= %s GROUP BY item_id, dat, typ;", + [timezone_offset, self.owner.id, int(max_visiblity)], ), ] for sql, params in queries: diff --git a/journal/templates/profile.html b/journal/templates/profile.html index b688bed7..43e79b51 100644 --- a/journal/templates/profile.html +++ b/journal/templates/profile.html @@ -26,7 +26,7 @@ title="{{ site_name }} - {{ identity.handler }}的评论" href="{{ request.build_absolute_uri }}feed/reviews/"> {% include "common_libs.html" with jquery=0 v2=1 %} - + diff --git a/neodb-takahe b/neodb-takahe index bb145fa4..ea3a8d36 160000 --- a/neodb-takahe +++ b/neodb-takahe @@ -1 +1 @@ -Subproject commit bb145fa4ae8d68810ee1963e9b24b3e7623a7069 +Subproject commit ea3a8d36644e4864aa5a55b1b207397eeebb9376 diff --git a/takahe/migrations/0001_initial.py b/takahe/migrations/0001_initial.py index 36567011..9edff303 100644 --- a/takahe/migrations/0001_initial.py +++ b/takahe/migrations/0001_initial.py @@ -647,4 +647,80 @@ class Migration(migrations.Migration): "db_table": "users_invite", }, ), + migrations.CreateModel( + name="FanOut", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("state", models.CharField(default="outdated", max_length=100)), + ("state_changed", models.DateTimeField(auto_now_add=True)), + ( + "type", + models.CharField( + choices=[ + ("post", "Post"), + ("post_edited", "Post Edited"), + ("post_deleted", "Post Deleted"), + ("interaction", "Interaction"), + ("undo_interaction", "Undo Interaction"), + ("identity_edited", "Identity Edited"), + ("identity_deleted", "Identity Deleted"), + ("identity_created", "Identity Created"), + ("identity_moved", "Identity Moved"), + ], + max_length=100, + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ( + "identity", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="fan_outs", + to="takahe.identity", + ), + ), + ( + "subject_identity", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="subject_fan_outs", + to="takahe.identity", + ), + ), + ( + "subject_post", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="fan_outs", + to="takahe.post", + ), + ), + ( + "subject_post_interaction", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="fan_outs", + to="takahe.postinteraction", + ), + ), + ], + options={ + "db_table": "activities_fanout", + }, + ), ] diff --git a/takahe/models.py b/takahe/models.py index 14f68e58..1be80dd8 100644 --- a/takahe/models.py +++ b/takahe/models.py @@ -1032,7 +1032,9 @@ def create_local( post.emojis.set(emojis) if published and published < timezone.now(): post.published = published - if timezone.now() - published > datetime.timedelta(days=9): + if timezone.now() - published > datetime.timedelta( + days=settings.FANOUT_LIMIT_DAYS + ): post.state = "fanned_out" # add post quietly if it's old # if attachments:# FIXME # post.attachments.set(attachments) @@ -1045,6 +1047,13 @@ def create_local( # Recalculate parent stats for replies if reply_to: reply_to.calculate_stats() + if post.state == "fanned_out": + FanOut.objects.create( + identity=author, + type="post", + subject_post=post, + ) + return post def edit_local( @@ -1137,6 +1146,69 @@ def safe_content_local(self): return ContentRenderer(local=True).render_post(self.content, self) +class FanOut(models.Model): + """ + An activity that needs to get to an inbox somewhere. + """ + + class Meta: + # managed = False + db_table = "activities_fanout" + + class Types(models.TextChoices): + post = "post" + post_edited = "post_edited" + post_deleted = "post_deleted" + interaction = "interaction" + undo_interaction = "undo_interaction" + identity_edited = "identity_edited" + identity_deleted = "identity_deleted" + identity_created = "identity_created" + identity_moved = "identity_moved" + + state = models.CharField(max_length=100, default="outdated") + state_changed = models.DateTimeField(auto_now_add=True) + + # The user this event is targeted at + # We always need this, but if there is a shared inbox URL on the user + # we'll deliver to that and won't have fanouts for anyone else with the + # same one. + identity = models.ForeignKey( + "takahe.Identity", + on_delete=models.CASCADE, + related_name="fan_outs", + ) + + # What type of activity it is + type = models.CharField(max_length=100, choices=Types.choices) + + # Links to the appropriate objects + subject_post = models.ForeignKey( + "takahe.Post", + on_delete=models.CASCADE, + blank=True, + null=True, + related_name="fan_outs", + ) + subject_post_interaction = models.ForeignKey( + "takahe.PostInteraction", + on_delete=models.CASCADE, + blank=True, + null=True, + related_name="fan_outs", + ) + subject_identity = models.ForeignKey( + "takahe.Identity", + on_delete=models.CASCADE, + blank=True, + null=True, + related_name="subject_fan_outs", + ) + + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) + + class EmojiQuerySet(models.QuerySet): def usable(self, domain: Domain | None = None): """