aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorwinter2024-12-20 17:57:36 +0000
committerwinter2024-12-20 18:01:26 +0000
commit8ca2452dd05f614d55d2fd3ce188d9d04f44d285 (patch)
tree7c850737c216df179ce00fa874b1de508894eb71
parent583b1f8a865935dd4ae1b0c9fa40a4b347ea3d4d (diff)
implement deletion/tombstones?
-rw-r--r--Digitigrade/Job/ProcessIncomingPush.php9
-rw-r--r--Digitigrade/Model/Actor.php15
-rw-r--r--Digitigrade/Model/FetchableModel.php10
-rw-r--r--Digitigrade/Model/Interaction.php15
-rw-r--r--Digitigrade/Model/Note.php23
-rw-r--r--Digitigrade/Model/PushableModel.php42
-rw-r--r--migrations/20241220_152023_deletion.php60
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();
+});