From 3b079526a2ea78b68555094ca498faea31022759 Mon Sep 17 00:00:00 2001
From: Andrew Godwin
Date: Sun, 27 Nov 2022 17:05:31 -0700
Subject: User fetching and inbox message cleaning

---
 users/admin.py                                  |  2 +-
 users/migrations/0003_identity_followers_etc.py | 38 +++++++++++++++++++++++++
 users/models/identity.py                        | 20 ++++++++++---
 users/models/inbox_message.py                   | 10 ++++++-
 4 files changed, 64 insertions(+), 6 deletions(-)
 create mode 100644 users/migrations/0003_identity_followers_etc.py

(limited to 'users')

diff --git a/users/admin.py b/users/admin.py
index f0d484d..235b0db 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -65,7 +65,7 @@ class PasswordResetAdmin(admin.ModelAdmin):
 
 @admin.register(InboxMessage)
 class InboxMessageAdmin(admin.ModelAdmin):
-    list_display = ["id", "state", "state_attempted", "message_type", "message_actor"]
+    list_display = ["id", "state", "state_changed", "message_type", "message_actor"]
     search_fields = ["message"]
     actions = ["reset_state"]
     readonly_fields = ["state_changed"]
diff --git a/users/migrations/0003_identity_followers_etc.py b/users/migrations/0003_identity_followers_etc.py
new file mode 100644
index 0000000..ffb6272
--- /dev/null
+++ b/users/migrations/0003_identity_followers_etc.py
@@ -0,0 +1,38 @@
+# Generated by Django 4.1.3 on 2022-11-27 22:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("users", "0002_identity_discoverable"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="identity",
+            name="followers_uri",
+            field=models.CharField(blank=True, max_length=500, null=True),
+        ),
+        migrations.AddField(
+            model_name="identity",
+            name="following_uri",
+            field=models.CharField(blank=True, max_length=500, null=True),
+        ),
+        migrations.AddField(
+            model_name="identity",
+            name="metadata",
+            field=models.JSONField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name="identity",
+            name="pinned",
+            field=models.JSONField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name="identity",
+            name="shared_inbox_uri",
+            field=models.CharField(blank=True, max_length=500, null=True),
+        ),
+    ]
diff --git a/users/models/identity.py b/users/models/identity.py
index 805755a..6957526 100644
--- a/users/models/identity.py
+++ b/users/models/identity.py
@@ -23,19 +23,31 @@ from users.models.system_actor import SystemActor
 
 
 class IdentityStates(StateGraph):
-    outdated = State(try_interval=3600)
-    updated = State()
+    """
+    There are only two states in a cycle.
+    Identities sit in "updated" for up to system.identity_max_age, and then
+    go back to "outdated" for refetching.
+    """
+
+    outdated = State(try_interval=3600, force_initial=True)
+    updated = State(try_interval=86400, attempt_immediately=False)
 
     outdated.transitions_to(updated)
+    updated.transitions_to(outdated)
 
     @classmethod
     async def handle_outdated(cls, identity: "Identity"):
         # Local identities never need fetching
         if identity.local:
-            return "updated"
+            return cls.updated
         # Run the actor fetch and progress to updated if it succeeds
         if await identity.fetch_actor():
-            return "updated"
+            return cls.updated
+
+    @classmethod
+    async def handle_updated(cls, instance: "Identity"):
+        if instance.state_age > Config.system.identity_max_age:
+            return cls.outdated
 
 
 class Identity(StatorModel):
diff --git a/users/models/inbox_message.py b/users/models/inbox_message.py
index 589f933..079c572 100644
--- a/users/models/inbox_message.py
+++ b/users/models/inbox_message.py
@@ -1,14 +1,17 @@
 from asgiref.sync import sync_to_async
 from django.db import models
 
+from core.models import Config
 from stator.models import State, StateField, StateGraph, StatorModel
 
 
 class InboxMessageStates(StateGraph):
     received = State(try_interval=300)
-    processed = State()
+    processed = State(try_interval=86400, attempt_immediately=False)
+    purged = State()  # This is actually deletion, it will never get here
 
     received.transitions_to(processed)
+    processed.transitions_to(purged)
 
     @classmethod
     async def handle_received(cls, instance: "InboxMessage"):
@@ -80,6 +83,11 @@ class InboxMessageStates(StateGraph):
                 raise ValueError(f"Cannot handle activity of type {unknown}")
         return cls.processed
 
+    @classmethod
+    async def handle_processed(cls, instance: "InboxMessage"):
+        if instance.state_age > Config.system.inbox_message_purge_after:
+            await InboxMessage.objects.filter(pk=instance.pk).adelete()
+
 
 class InboxMessage(StatorModel):
     """
-- 
cgit v1.2.3