forked from forks/microblog.pub
Start support for deleting remote actors
This commit is contained in:
parent
02c09f2363
commit
e3a02a8138
4 changed files with 158 additions and 18 deletions
|
@ -0,0 +1,32 @@
|
||||||
|
"""Add Actor.is_deleted
|
||||||
|
|
||||||
|
Revision ID: 5d3e3f2b9b4e
|
||||||
|
Revises: 6286262bb466
|
||||||
|
Create Date: 2022-08-17 17:58:24.813194+00:00
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '5d3e3f2b9b4e'
|
||||||
|
down_revision = '6286262bb466'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('actor', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('is_deleted', sa.Boolean(), server_default='0', nullable=False))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('actor', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('is_deleted')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
|
@ -176,7 +176,10 @@ async def fetch_actor(
|
||||||
|
|
||||||
existing_actor = (
|
existing_actor = (
|
||||||
await db_session.scalars(
|
await db_session.scalars(
|
||||||
select(models.Actor).where(models.Actor.ap_id == actor_id)
|
select(models.Actor).where(
|
||||||
|
models.Actor.ap_id == actor_id,
|
||||||
|
models.Actor.is_deleted.is_(False),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
).one_or_none()
|
).one_or_none()
|
||||||
if existing_actor:
|
if existing_actor:
|
||||||
|
|
138
app/boxes.py
138
app/boxes.py
|
@ -839,7 +839,7 @@ async def _handle_delete_activity(
|
||||||
db_session: AsyncSession,
|
db_session: AsyncSession,
|
||||||
from_actor: models.Actor,
|
from_actor: models.Actor,
|
||||||
delete_activity: models.InboxObject,
|
delete_activity: models.InboxObject,
|
||||||
ap_object_to_delete: models.InboxObject | None,
|
ap_object_to_delete: models.InboxObject | models.Actor | None,
|
||||||
forwarded_by_actor: models.Actor | None,
|
forwarded_by_actor: models.Actor | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if ap_object_to_delete is None:
|
if ap_object_to_delete is None:
|
||||||
|
@ -847,24 +847,77 @@ async def _handle_delete_activity(
|
||||||
"Received Delete for an unknown object "
|
"Received Delete for an unknown object "
|
||||||
f"{delete_activity.activity_object_ap_id}"
|
f"{delete_activity.activity_object_ap_id}"
|
||||||
)
|
)
|
||||||
# TODO(tsileo): support deleting actor
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if from_actor.ap_id != ap_object_to_delete.actor.ap_id:
|
if isinstance(ap_object_to_delete, models.InboxObject):
|
||||||
logger.warning(
|
if from_actor.ap_id != ap_object_to_delete.actor.ap_id:
|
||||||
"Actor mismatch between the activity and the object: "
|
logger.warning(
|
||||||
f"{from_actor.ap_id}/{ap_object_to_delete.actor.ap_id}"
|
"Actor mismatch between the activity and the object: "
|
||||||
|
f"{from_actor.ap_id}/{ap_object_to_delete.actor.ap_id}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Deleting {ap_object_to_delete.ap_type}/{ap_object_to_delete.ap_id}"
|
||||||
)
|
)
|
||||||
return
|
await _revert_side_effect_for_deleted_object(
|
||||||
|
db_session,
|
||||||
|
delete_activity,
|
||||||
|
ap_object_to_delete,
|
||||||
|
forwarded_by_actor,
|
||||||
|
)
|
||||||
|
ap_object_to_delete.is_deleted = True
|
||||||
|
elif isinstance(ap_object_to_delete, models.Actor):
|
||||||
|
if from_actor.ap_id != ap_object_to_delete.ap_id:
|
||||||
|
logger.warning(
|
||||||
|
"Actor mismatch between the activity and the object: "
|
||||||
|
f"{from_actor.ap_id}/{ap_object_to_delete.ap_id}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
logger.info(f"Deleting {ap_object_to_delete.ap_type}/{ap_object_to_delete.ap_id}")
|
logger.info(f"Deleting actor {ap_object_to_delete.ap_id}")
|
||||||
await _revert_side_effect_for_deleted_object(
|
follower = (
|
||||||
db_session,
|
await db_session.scalars(
|
||||||
delete_activity,
|
select(models.Follower).where(
|
||||||
ap_object_to_delete,
|
models.Follower.ap_actor_id == ap_object_to_delete.ap_id,
|
||||||
forwarded_by_actor,
|
)
|
||||||
)
|
)
|
||||||
ap_object_to_delete.is_deleted = True
|
).one_or_none()
|
||||||
|
if follower:
|
||||||
|
logger.info("Removing actor from follower")
|
||||||
|
await db_session.delete(follower)
|
||||||
|
|
||||||
|
following = (
|
||||||
|
await db_session.scalars(
|
||||||
|
select(models.Following).where(
|
||||||
|
models.Following.ap_actor_id == ap_object_to_delete.ap_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).one_or_none()
|
||||||
|
if following:
|
||||||
|
logger.info("Removing actor from following")
|
||||||
|
await db_session.delete(following)
|
||||||
|
|
||||||
|
# Mark the actor as deleted
|
||||||
|
ap_object_to_delete.is_deleted = True
|
||||||
|
|
||||||
|
inbox_objects = (
|
||||||
|
await db_session.scalars(
|
||||||
|
select(models.InboxObject).where(
|
||||||
|
models.InboxObject.actor_id == ap_object_to_delete.id,
|
||||||
|
models.InboxObject.is_deleted.is_(False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
logger.info(f"Deleting {len(inbox_objects)} objects")
|
||||||
|
for inbox_object in inbox_objects:
|
||||||
|
await _revert_side_effect_for_deleted_object(
|
||||||
|
db_session,
|
||||||
|
delete_activity,
|
||||||
|
inbox_object,
|
||||||
|
forwarded_by_actor=None,
|
||||||
|
)
|
||||||
|
inbox_object.is_deleted = True
|
||||||
|
|
||||||
await db_session.flush()
|
await db_session.flush()
|
||||||
|
|
||||||
|
@ -905,6 +958,38 @@ async def _revert_side_effect_for_deleted_object(
|
||||||
.values(replies_count=models.InboxObject.replies_count - 1)
|
.values(replies_count=models.InboxObject.replies_count - 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if deleted_ap_object.ap_type == "Like" and deleted_ap_object.activity_object_ap_id:
|
||||||
|
related_object = await get_outbox_object_by_ap_id(
|
||||||
|
db_session,
|
||||||
|
deleted_ap_object.activity_object_ap_id,
|
||||||
|
)
|
||||||
|
if related_object:
|
||||||
|
if related_object.is_from_outbox:
|
||||||
|
await db_session.execute(
|
||||||
|
update(models.OutboxObject)
|
||||||
|
.where(
|
||||||
|
models.OutboxObject.id == related_object.id,
|
||||||
|
)
|
||||||
|
.values(likes_count=models.OutboxObject.likes_count - 1)
|
||||||
|
)
|
||||||
|
elif (
|
||||||
|
deleted_ap_object.ap_type == "Annouce"
|
||||||
|
and deleted_ap_object.activity_object_ap_id
|
||||||
|
):
|
||||||
|
related_object = await get_outbox_object_by_ap_id(
|
||||||
|
db_session,
|
||||||
|
deleted_ap_object.activity_object_ap_id,
|
||||||
|
)
|
||||||
|
if related_object:
|
||||||
|
if related_object.is_from_outbox:
|
||||||
|
await db_session.execute(
|
||||||
|
update(models.OutboxObject)
|
||||||
|
.where(
|
||||||
|
models.OutboxObject.id == related_object.id,
|
||||||
|
)
|
||||||
|
.values(announces_count=models.OutboxObject.announces_count - 1)
|
||||||
|
)
|
||||||
|
|
||||||
# Delete any Like/Announce
|
# Delete any Like/Announce
|
||||||
await db_session.execute(
|
await db_session.execute(
|
||||||
update(models.OutboxObject)
|
update(models.OutboxObject)
|
||||||
|
@ -916,7 +1001,11 @@ async def _revert_side_effect_for_deleted_object(
|
||||||
|
|
||||||
# If it's a local replies, it was forwarded, so we also need to forward
|
# If it's a local replies, it was forwarded, so we also need to forward
|
||||||
# the Delete activity if possible
|
# the Delete activity if possible
|
||||||
if delete_activity.has_ld_signature and is_delete_needs_to_be_forwarded:
|
if (
|
||||||
|
delete_activity.activity_object_ap_id == deleted_ap_object.ap_id
|
||||||
|
and delete_activity.has_ld_signature
|
||||||
|
and is_delete_needs_to_be_forwarded
|
||||||
|
):
|
||||||
logger.info("Forwarding Delete activity as it's a local reply")
|
logger.info("Forwarding Delete activity as it's a local reply")
|
||||||
|
|
||||||
# Don't forward to the forwarding actor and the original Delete actor
|
# Don't forward to the forwarding actor and the original Delete actor
|
||||||
|
@ -1638,11 +1727,26 @@ async def save_to_inbox(
|
||||||
elif activity_ro.ap_type == "Move":
|
elif activity_ro.ap_type == "Move":
|
||||||
await _handle_move_activity(db_session, actor, inbox_object)
|
await _handle_move_activity(db_session, actor, inbox_object)
|
||||||
elif activity_ro.ap_type == "Delete":
|
elif activity_ro.ap_type == "Delete":
|
||||||
|
object_to_delete: models.InboxObject | models.Actor | None
|
||||||
|
if relates_to_inbox_object:
|
||||||
|
object_to_delete = relates_to_inbox_object
|
||||||
|
elif inbox_object.activity_object_ap_id:
|
||||||
|
# If it's not a Delete for an inbox object, it may be related to
|
||||||
|
# an actor
|
||||||
|
try:
|
||||||
|
object_to_delete = await fetch_actor(
|
||||||
|
db_session,
|
||||||
|
inbox_object.activity_object_ap_id,
|
||||||
|
save_if_not_found=False,
|
||||||
|
)
|
||||||
|
except ap.ObjectNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
await _handle_delete_activity(
|
await _handle_delete_activity(
|
||||||
db_session,
|
db_session,
|
||||||
actor,
|
actor,
|
||||||
inbox_object,
|
inbox_object,
|
||||||
relates_to_inbox_object,
|
object_to_delete,
|
||||||
forwarded_by_actor=forwarded_by_actor,
|
forwarded_by_actor=forwarded_by_actor,
|
||||||
)
|
)
|
||||||
elif activity_ro.ap_type == "Follow":
|
elif activity_ro.ap_type == "Follow":
|
||||||
|
|
|
@ -52,6 +52,7 @@ class Actor(Base, BaseActor):
|
||||||
handle = Column(String, nullable=True, index=True)
|
handle = Column(String, nullable=True, index=True)
|
||||||
|
|
||||||
is_blocked = Column(Boolean, nullable=False, default=False, server_default="0")
|
is_blocked = Column(Boolean, nullable=False, default=False, server_default="0")
|
||||||
|
is_deleted = Column(Boolean, nullable=False, default=False, server_default="0")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_from_db(self) -> bool:
|
def is_from_db(self) -> bool:
|
||||||
|
|
Loading…
Reference in a new issue