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):
"""