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 @@ env = environ.FileAwareEnv(
# 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_CACHE_TIMEOUT = env("NEODB_DOWNLOADER_CACHE_TIMEOUT")
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/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/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 @@ class Post(models.Model):
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 @@ class Post(models.Model):
# 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 @@ class Post(models.Model):
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):
"""