diff options
| author | winter | 2024-12-20 17:57:36 +0000 |
|---|---|---|
| committer | winter | 2024-12-20 18:01:26 +0000 |
| commit | 8ca2452dd05f614d55d2fd3ce188d9d04f44d285 (patch) | |
| tree | 7c850737c216df179ce00fa874b1de508894eb71 | |
| parent | 583b1f8a865935dd4ae1b0c9fa40a4b347ea3d4d (diff) | |
implement deletion/tombstones?
| -rw-r--r-- | Digitigrade/Job/ProcessIncomingPush.php | 9 | ||||
| -rw-r--r-- | Digitigrade/Model/Actor.php | 15 | ||||
| -rw-r--r-- | Digitigrade/Model/FetchableModel.php | 10 | ||||
| -rw-r--r-- | Digitigrade/Model/Interaction.php | 15 | ||||
| -rw-r--r-- | Digitigrade/Model/Note.php | 23 | ||||
| -rw-r--r-- | Digitigrade/Model/PushableModel.php | 42 | ||||
| -rw-r--r-- | migrations/20241220_152023_deletion.php | 60 |
7 files changed, 158 insertions, 16 deletions
diff --git a/Digitigrade/Job/ProcessIncomingPush.php b/Digitigrade/Job/ProcessIncomingPush.php index fefa122..4f6d955 100644 --- a/Digitigrade/Job/ProcessIncomingPush.php +++ b/Digitigrade/Job/ProcessIncomingPush.php @@ -18,6 +18,14 @@ class ProcessIncomingPush extends Job { public function run() { assert(isset($this->object->type)); $log = Logger::getInstance(); + $type = $this->object->type; + if ($type == 'tombstone') { + if (!isset($this->object->previousType)) { + throw new \RuntimeException('tombstone object does not have a previousType'); + } + $log->info('the following object import is actually a tombstone ...'); + $type = $this->object->previousType; + } switch ($this->object->type) { case 'actor': $log->info('importing actor with uri ' . $this->object->self); @@ -32,7 +40,6 @@ class ProcessIncomingPush extends Job { Interaction::importFromReceivedObject($this->object); break; case 'extension': - case 'tombstone': throw new \RuntimeException('object type ' . $this->object->type . ' not yet implemented :('); default: throw new \RuntimeException('invalid object type: ' . $this->object->type); diff --git a/Digitigrade/Model/Actor.php b/Digitigrade/Model/Actor.php index 07f24ec..8059409 100644 --- a/Digitigrade/Model/Actor.php +++ b/Digitigrade/Model/Actor.php @@ -196,7 +196,22 @@ class Actor extends PushableModel implements RpcReceiver { return Instance::findByHostname(hostname_from_uri($this->uri)); } + protected function getRelevantServers(): array { + $actors = array_unique(array_merge($this->findFollowers(), $this->findFollowees())); + $instances = array_unique(array_map(function (Actor $actor) { + return Instance::findByHostname(hostname_from_uri($actor->uri)); + }, $actors)); + return $instances; + } + public function jsonSerialize(): array { + if ($this->deleted) { + return [ + 'type' => 'tombstone', + 'self' => path_to_uri("/user/$this->handle"), + 'previousType' => 'actor' + ]; + } return [ 'type' => 'actor', 'self' => path_to_uri("/user/$this->handle"), diff --git a/Digitigrade/Model/FetchableModel.php b/Digitigrade/Model/FetchableModel.php index 5e4d1e8..e14cc2e 100644 --- a/Digitigrade/Model/FetchableModel.php +++ b/Digitigrade/Model/FetchableModel.php @@ -62,6 +62,16 @@ abstract class FetchableModel extends Model implements RemoteFetchable { if ($data === null) return null; + if (($data->type ?? null) == 'tombstone') { + // handle deletes + $obj = static::findWhere('uri = ?', [$data->self]); + if ($obj == null) + return null; + $obj->deleted = true; + $obj->save(); + return null; + } + $obj = static::createFromJson($data, $autoSave, $uri); $obj->finaliseAfterFetch($uri); diff --git a/Digitigrade/Model/Interaction.php b/Digitigrade/Model/Interaction.php index bf0745e..38065c1 100644 --- a/Digitigrade/Model/Interaction.php +++ b/Digitigrade/Model/Interaction.php @@ -47,7 +47,22 @@ class Interaction extends PushableModel { return self::findAllWhere('target = ?', [$target->id]); } + protected function getRelevantServers(): array { + $instances = array_map(function (Actor $actor) { + return Instance::findByHostname(hostname_from_uri($actor->uri)); + }, $this->author->findFollowers()); + $instances[] = Instance::findByHostname(hostname_from_uri($this->target->uri)); + return $instances; + } + public function jsonSerialize(): array { + if ($this->deleted) { + return [ + 'type' => 'tombstone', + 'self' => path_to_uri("/interaction/$this->id"), + 'previousType' => 'interaction' + ]; + } return [ 'type' => 'interaction', 'self' => path_to_uri("/interaction/$this->id"), diff --git a/Digitigrade/Model/Note.php b/Digitigrade/Model/Note.php index 7ffb67e..9d73a89 100644 --- a/Digitigrade/Model/Note.php +++ b/Digitigrade/Model/Note.php @@ -110,15 +110,7 @@ class Note extends PushableModel { return Interaction::findAllWithTarget($this); } - /** - * Sends this note to all instances it's relevant to. - * - * For example, this may include the servers of any followers if the scope is at - * least 'followers', anyone mentioned, and anyone in the alsoVisibleTo field. - * If the scope is 'public' it may also send it to other servers? i'm not sure - * @return void - */ - public function publish() { + protected function getRelevantServers(): array { $recipientActors = match ($this->privacy->scope) { NotePrivacyScope::NONE => [], NotePrivacyScope::MUTUALS => $this->author->findMutualFollows(), @@ -129,14 +121,17 @@ class Note extends PushableModel { $instances = array_map(function (Actor $a) { return $a->findHomeInstance(); }, $recipientActors); - $instances = array_unique($instances); - - foreach ($instances as $instance) { - (new PushObject($this, $instance))->submit(); - } + return array_unique($instances); } public function jsonSerialize(): array { + if ($this->deleted) { + return [ + 'type' => 'tombstone', + 'self' => path_to_uri("/post/$this->id"), + 'previousType' => 'note' + ]; + } return [ 'type' => 'note', 'self' => path_to_uri("/post/$this->id"), diff --git a/Digitigrade/Model/PushableModel.php b/Digitigrade/Model/PushableModel.php index c008320..9de3507 100644 --- a/Digitigrade/Model/PushableModel.php +++ b/Digitigrade/Model/PushableModel.php @@ -1,11 +1,24 @@ <?php namespace Digitigrade\Model; +use Digitigrade\Job\PushObject; + abstract class PushableModel extends FetchableModel implements \JsonSerializable { public ?int $id; public string $uri; + public bool $deleted = false; + + public static function importFromReceivedObject(\stdClass $data, bool $autoSave = true): ?static { + if ($data->type == 'tombstone') { + // actually just delete the object if we know it and do nothing if not + $obj = static::findWhere('uri = ?', [$data->self]); + if ($obj == null) + return null; + $obj->deleted = true; + $obj->save(); + return null; + } - public static function importFromReceivedObject(\stdClass $data, bool $autoSave = true): static { $obj = static::createFromJson($data, $autoSave); if ($autoSave) { $obj->save(); @@ -13,4 +26,31 @@ abstract class PushableModel extends FetchableModel implements \JsonSerializable } return $obj; } + + /** + * @return Instance[] a list of instances who this object should be pushed to + */ + protected abstract function getRelevantServers(): array; + + /** + * Sends this object to all instances it's relevant to. + * + * Please don't call this on non-local objects :3 + * @return void + */ + public function publish() { + foreach ($this->getRelevantServers() as $instance) { + (new PushObject($this, $instance))->submit(); + } + } + + /** + * Marks this object as deleted and publishes a tombstone in its place. + * @return void + */ + public function markDeleted() { + $this->deleted = true; + $this->save(); + $this->publish(); + } }
\ No newline at end of file diff --git a/migrations/20241220_152023_deletion.php b/migrations/20241220_152023_deletion.php new file mode 100644 index 0000000..3b5dd9b --- /dev/null +++ b/migrations/20241220_152023_deletion.php @@ -0,0 +1,60 @@ +<?php + +use \Digitigrade\Db\Migrator; + +Migrator::getInstance()->register(20241220_152023, function (PDO $db) { + // all fetchable/pushable objects can be marked as deleted, which will hide them, and make them get swept up later + // when they're actually deleted i also need any referents to be cascade-deleted + // also i noticed the column type for note.in_reply_to is wrong lol + $db->beginTransaction(); + $db->exec(<<<END + ALTER TABLE actor ADD COLUMN deleted boolean not null default false; + ALTER TABLE note ADD COLUMN deleted boolean not null default false; + ALTER TABLE interaction ADD COLUMN deleted boolean not null default false; + + ALTER TABLE actor_endpoints + DROP CONSTRAINT actor_endpoints_actor_id_fkey, + ADD foreign key (actor_id) references actor on delete cascade; + ALTER TABLE actor_extension + DROP CONSTRAINT actor_extension_actor_id_fkey, + ADD foreign key (actor_id) references actor on delete cascade; + ALTER TABLE actor_webfinger + DROP CONSTRAINT actor_webfinger_actor_fkey, + ADD foreign key (actor) references actor on delete cascade; + ALTER TABLE follow_relation + DROP CONSTRAINT follow_relation_object_fkey, + DROP CONSTRAINT follow_relation_subject_fkey, + ADD foreign key (object) references actor on delete cascade, + ADD foreign key (subject) references actor on delete cascade; + ALTER TABLE interaction + DROP CONSTRAINT interaction_author_fkey, + DROP CONSTRAINT interaction_target_fkey, + ADD foreign key (author) references actor on delete cascade, + ADD foreign key (target) references note on delete cascade; + ALTER TABLE note + DROP CONSTRAINT note_author_fkey, + DROP CONSTRAINT note_thread_apex_fkey, + DROP COLUMN in_reply_to, + ADD COLUMN in_reply_to bigint references note on delete set null, + ADD foreign key (author) references actor on delete cascade, + ADD foreign key (thread_apex) references note on delete set null; + ALTER TABLE note_attachment + DROP CONSTRAINT note_attachment_note_id_fkey, + ADD foreign key (note_id) references note on delete cascade; + ALTER TABLE note_extension + DROP CONSTRAINT note_extension_note_id_fkey, + ADD foreign key (note_id) references note on delete cascade; + ALTER TABLE note_formatted_content + DROP CONSTRAINT note_formatted_content_note_id_fkey, + ADD foreign key (note_id) references note on delete cascade; + ALTER TABLE note_mention + DROP CONSTRAINT note_mention_actor_id_fkey, + DROP CONSTRAINT note_mention_note_id_fkey, + ADD foreign key (actor_id) references actor on delete cascade, + ADD foreign key (note_id) references note on delete cascade; + ALTER TABLE note_privacy + DROP CONSTRAINT note_privacy_note_id_fkey, + ADD foreign key (note_id) references note on delete cascade; + END); + $db->commit(); +}); |
