User fetching and inbox message cleaning
This commit is contained in:
parent
2f443414a7
commit
3b079526a2
@ -213,6 +213,7 @@ class Config(models.Model):
|
|||||||
identity_min_length: int = 2
|
identity_min_length: int = 2
|
||||||
identity_max_per_user: int = 5
|
identity_max_per_user: int = 5
|
||||||
identity_max_age: int = 24 * 60 * 60
|
identity_max_age: int = 24 * 60 * 60
|
||||||
|
inbox_message_purge_after: int = 24 * 60 * 60
|
||||||
|
|
||||||
restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"
|
restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"
|
||||||
|
|
||||||
|
@ -87,10 +87,14 @@ class State:
|
|||||||
try_interval: Optional[float] = None,
|
try_interval: Optional[float] = None,
|
||||||
handler_name: Optional[str] = None,
|
handler_name: Optional[str] = None,
|
||||||
externally_progressed: bool = False,
|
externally_progressed: bool = False,
|
||||||
|
attempt_immediately: bool = True,
|
||||||
|
force_initial: bool = False,
|
||||||
):
|
):
|
||||||
self.try_interval = try_interval
|
self.try_interval = try_interval
|
||||||
self.handler_name = handler_name
|
self.handler_name = handler_name
|
||||||
self.externally_progressed = externally_progressed
|
self.externally_progressed = externally_progressed
|
||||||
|
self.attempt_immediately = attempt_immediately
|
||||||
|
self.force_initial = force_initial
|
||||||
self.parents: Set["State"] = set()
|
self.parents: Set["State"] = set()
|
||||||
self.children: Set["State"] = set()
|
self.children: Set["State"] = set()
|
||||||
|
|
||||||
@ -121,7 +125,7 @@ class State:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def initial(self):
|
def initial(self):
|
||||||
return not self.parents
|
return self.force_initial or (not self.parents)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def terminal(self):
|
def terminal(self):
|
||||||
|
@ -74,6 +74,10 @@ class StatorModel(models.Model):
|
|||||||
def state_graph(cls) -> Type[StateGraph]:
|
def state_graph(cls) -> Type[StateGraph]:
|
||||||
return cls._meta.get_field("state").graph
|
return cls._meta.get_field("state").graph
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_age(self) -> int:
|
||||||
|
return (timezone.now() - self.state_changed).total_seconds()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def atransition_schedule_due(cls, now=None) -> models.QuerySet:
|
async def atransition_schedule_due(cls, now=None) -> models.QuerySet:
|
||||||
"""
|
"""
|
||||||
@ -184,13 +188,23 @@ class StatorModel(models.Model):
|
|||||||
state = state.name
|
state = state.name
|
||||||
if state not in self.state_graph.states:
|
if state not in self.state_graph.states:
|
||||||
raise ValueError(f"Invalid state {state}")
|
raise ValueError(f"Invalid state {state}")
|
||||||
self.__class__.objects.filter(pk=self.pk).update(
|
# See if it's ready immediately (if not, delay until first try_interval)
|
||||||
state=state,
|
if self.state_graph.states[state].attempt_immediately:
|
||||||
state_changed=timezone.now(),
|
self.__class__.objects.filter(pk=self.pk).update(
|
||||||
state_attempted=None,
|
state=state,
|
||||||
state_locked_until=None,
|
state_changed=timezone.now(),
|
||||||
state_ready=True,
|
state_attempted=None,
|
||||||
)
|
state_locked_until=None,
|
||||||
|
state_ready=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.__class__.objects.filter(pk=self.pk).update(
|
||||||
|
state=state,
|
||||||
|
state_changed=timezone.now(),
|
||||||
|
state_attempted=timezone.now(),
|
||||||
|
state_locked_until=None,
|
||||||
|
state_ready=False,
|
||||||
|
)
|
||||||
|
|
||||||
atransition_perform = sync_to_async(transition_perform)
|
atransition_perform = sync_to_async(transition_perform)
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ class PasswordResetAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(InboxMessage)
|
@admin.register(InboxMessage)
|
||||||
class InboxMessageAdmin(admin.ModelAdmin):
|
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"]
|
search_fields = ["message"]
|
||||||
actions = ["reset_state"]
|
actions = ["reset_state"]
|
||||||
readonly_fields = ["state_changed"]
|
readonly_fields = ["state_changed"]
|
||||||
|
38
users/migrations/0003_identity_followers_etc.py
Normal file
38
users/migrations/0003_identity_followers_etc.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -23,19 +23,31 @@ from users.models.system_actor import SystemActor
|
|||||||
|
|
||||||
|
|
||||||
class IdentityStates(StateGraph):
|
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)
|
outdated.transitions_to(updated)
|
||||||
|
updated.transitions_to(outdated)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def handle_outdated(cls, identity: "Identity"):
|
async def handle_outdated(cls, identity: "Identity"):
|
||||||
# Local identities never need fetching
|
# Local identities never need fetching
|
||||||
if identity.local:
|
if identity.local:
|
||||||
return "updated"
|
return cls.updated
|
||||||
# Run the actor fetch and progress to updated if it succeeds
|
# Run the actor fetch and progress to updated if it succeeds
|
||||||
if await identity.fetch_actor():
|
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):
|
class Identity(StatorModel):
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from core.models import Config
|
||||||
from stator.models import State, StateField, StateGraph, StatorModel
|
from stator.models import State, StateField, StateGraph, StatorModel
|
||||||
|
|
||||||
|
|
||||||
class InboxMessageStates(StateGraph):
|
class InboxMessageStates(StateGraph):
|
||||||
received = State(try_interval=300)
|
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)
|
received.transitions_to(processed)
|
||||||
|
processed.transitions_to(purged)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def handle_received(cls, instance: "InboxMessage"):
|
async def handle_received(cls, instance: "InboxMessage"):
|
||||||
@ -80,6 +83,11 @@ class InboxMessageStates(StateGraph):
|
|||||||
raise ValueError(f"Cannot handle activity of type {unknown}")
|
raise ValueError(f"Cannot handle activity of type {unknown}")
|
||||||
return cls.processed
|
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):
|
class InboxMessage(StatorModel):
|
||||||
"""
|
"""
|
||||||
|
Reference in New Issue
Block a user