Improve replies counter handling
This commit is contained in:
parent
3305d489ec
commit
54aa2f51f4
3 changed files with 72 additions and 17 deletions
65
app/boxes.py
65
app/boxes.py
|
@ -130,7 +130,11 @@ async def send_delete(db_session: AsyncSession, ap_object_id: str) -> None:
|
||||||
db_session, outbox_object_to_delete.in_reply_to
|
db_session, outbox_object_to_delete.in_reply_to
|
||||||
)
|
)
|
||||||
if replied_object:
|
if replied_object:
|
||||||
replied_object.replies_count = replied_object.replies_count - 1
|
new_replies_count = await _get_replies_count(
|
||||||
|
db_session, replied_object.ap_id
|
||||||
|
)
|
||||||
|
|
||||||
|
replied_object.replies_count = new_replies_count
|
||||||
if replied_object.replies_count < 0:
|
if replied_object.replies_count < 0:
|
||||||
logger.warning("negative replies count for {replied_object.ap_id}")
|
logger.warning("negative replies count for {replied_object.ap_id}")
|
||||||
replied_object.replies_count = 0
|
replied_object.replies_count = 0
|
||||||
|
@ -344,7 +348,7 @@ async def fetch_conversation_root(
|
||||||
is_root: bool = False,
|
is_root: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Some softwares do not set the context/conversation field (like Misskey).
|
"""Some softwares do not set the context/conversation field (like Misskey).
|
||||||
This means we have to track conversation ourselves. To do set, we fetch
|
This means we have to track conversation ourselves. To do so, we fetch
|
||||||
the root of the conversation and either:
|
the root of the conversation and either:
|
||||||
- use the context field if set
|
- use the context field if set
|
||||||
- or build a custom conversation ID
|
- or build a custom conversation ID
|
||||||
|
@ -366,7 +370,12 @@ async def fetch_conversation_root(
|
||||||
db_session, ap.get_actor_id(raw_reply)
|
db_session, ap.get_actor_id(raw_reply)
|
||||||
)
|
)
|
||||||
in_reply_to_object = RemoteObject(raw_reply, actor=raw_reply_actor)
|
in_reply_to_object = RemoteObject(raw_reply, actor=raw_reply_actor)
|
||||||
except (ap.ObjectNotFoundError, ap.ObjectIsGoneError, ap.NotAnObjectError):
|
except (
|
||||||
|
ap.ObjectNotFoundError,
|
||||||
|
ap.ObjectIsGoneError,
|
||||||
|
ap.NotAnObjectError,
|
||||||
|
ap.ObjectUnavailableError,
|
||||||
|
):
|
||||||
return await fetch_conversation_root(db_session, obj, is_root=True)
|
return await fetch_conversation_root(db_session, obj, is_root=True)
|
||||||
except httpx.HTTPStatusError as http_status_error:
|
except httpx.HTTPStatusError as http_status_error:
|
||||||
if 400 <= http_status_error.response.status_code < 500:
|
if 400 <= http_status_error.response.status_code < 500:
|
||||||
|
@ -1020,6 +1029,29 @@ async def _handle_delete_activity(
|
||||||
await db_session.flush()
|
await db_session.flush()
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_replies_count(
|
||||||
|
db_session: AsyncSession,
|
||||||
|
replied_object_ap_id: str,
|
||||||
|
) -> int:
|
||||||
|
return (
|
||||||
|
await db_session.scalar(
|
||||||
|
select(func.count(models.InboxObject.id)).where(
|
||||||
|
func.json_extract(models.InboxObject.ap_object, "$.inReplyTo")
|
||||||
|
== replied_object_ap_id,
|
||||||
|
models.InboxObject.is_deleted.is_(False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) + (
|
||||||
|
await db_session.scalar(
|
||||||
|
select(func.count(models.OutboxObject.id)).where(
|
||||||
|
func.json_extract(models.OutboxObject.ap_object, "$.inReplyTo")
|
||||||
|
== replied_object_ap_id,
|
||||||
|
models.OutboxObject.is_deleted.is_(False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _revert_side_effect_for_deleted_object(
|
async def _revert_side_effect_for_deleted_object(
|
||||||
db_session: AsyncSession,
|
db_session: AsyncSession,
|
||||||
delete_activity: models.InboxObject,
|
delete_activity: models.InboxObject,
|
||||||
|
@ -1040,20 +1072,28 @@ async def _revert_side_effect_for_deleted_object(
|
||||||
# also needs to be forwarded
|
# also needs to be forwarded
|
||||||
is_delete_needs_to_be_forwarded = True
|
is_delete_needs_to_be_forwarded = True
|
||||||
|
|
||||||
|
new_replies_count = await _get_replies_count(
|
||||||
|
db_session, replied_object.ap_id
|
||||||
|
)
|
||||||
|
|
||||||
await db_session.execute(
|
await db_session.execute(
|
||||||
update(models.OutboxObject)
|
update(models.OutboxObject)
|
||||||
.where(
|
.where(
|
||||||
models.OutboxObject.id == replied_object.id,
|
models.OutboxObject.id == replied_object.id,
|
||||||
)
|
)
|
||||||
.values(replies_count=models.OutboxObject.replies_count - 1)
|
.values(replies_count=new_replies_count)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
new_replies_count = await _get_replies_count(
|
||||||
|
db_session, replied_object.ap_id
|
||||||
|
)
|
||||||
|
|
||||||
await db_session.execute(
|
await db_session.execute(
|
||||||
update(models.InboxObject)
|
update(models.InboxObject)
|
||||||
.where(
|
.where(
|
||||||
models.InboxObject.id == replied_object.id,
|
models.InboxObject.id == replied_object.id,
|
||||||
)
|
)
|
||||||
.values(replies_count=models.InboxObject.replies_count - 1)
|
.values(replies_count=new_replies_count)
|
||||||
)
|
)
|
||||||
|
|
||||||
if deleted_ap_object.ap_type == "Like" and deleted_ap_object.activity_object_ap_id:
|
if deleted_ap_object.ap_type == "Like" and deleted_ap_object.activity_object_ap_id:
|
||||||
|
@ -1484,8 +1524,9 @@ async def _handle_create_activity(
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Got a Delete for {ro.ap_id} from {delete_object.actor.ap_id}??"
|
f"Got a Delete for {ro.ap_id} from {delete_object.actor.ap_id}??"
|
||||||
)
|
)
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
logger.info("Got a Delete for this object, deleting activity")
|
logger.info("Already received a Delete for this object, deleting activity")
|
||||||
create_activity.is_deleted = True
|
create_activity.is_deleted = True
|
||||||
await db_session.flush()
|
await db_session.flush()
|
||||||
return None
|
return None
|
||||||
|
@ -1591,20 +1632,28 @@ async def _process_note_object(
|
||||||
replied_object, # type: ignore # outbox check below
|
replied_object, # type: ignore # outbox check below
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
new_replies_count = await _get_replies_count(
|
||||||
|
db_session, replied_object.ap_id
|
||||||
|
)
|
||||||
|
|
||||||
await db_session.execute(
|
await db_session.execute(
|
||||||
update(models.OutboxObject)
|
update(models.OutboxObject)
|
||||||
.where(
|
.where(
|
||||||
models.OutboxObject.id == replied_object.id,
|
models.OutboxObject.id == replied_object.id,
|
||||||
)
|
)
|
||||||
.values(replies_count=models.OutboxObject.replies_count + 1)
|
.values(replies_count=new_replies_count)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
new_replies_count = await _get_replies_count(
|
||||||
|
db_session, replied_object.ap_id
|
||||||
|
)
|
||||||
|
|
||||||
await db_session.execute(
|
await db_session.execute(
|
||||||
update(models.InboxObject)
|
update(models.InboxObject)
|
||||||
.where(
|
.where(
|
||||||
models.InboxObject.id == replied_object.id,
|
models.InboxObject.id == replied_object.id,
|
||||||
)
|
)
|
||||||
.values(replies_count=models.InboxObject.replies_count + 1)
|
.values(replies_count=new_replies_count)
|
||||||
)
|
)
|
||||||
|
|
||||||
# This object is a reply of a local object, we may need to forward it
|
# This object is a reply of a local object, we may need to forward it
|
||||||
|
|
|
@ -75,7 +75,7 @@ class InboxObject(Base, BaseObject):
|
||||||
|
|
||||||
ap_actor_id = Column(String, nullable=False)
|
ap_actor_id = Column(String, nullable=False)
|
||||||
ap_type = Column(String, nullable=False, index=True)
|
ap_type = Column(String, nullable=False, index=True)
|
||||||
ap_id = Column(String, nullable=False, unique=True, index=True)
|
ap_id: Mapped[str] = Column(String, nullable=False, unique=True, index=True)
|
||||||
ap_context = Column(String, nullable=True)
|
ap_context = Column(String, nullable=True)
|
||||||
ap_published_at = Column(DateTime(timezone=True), nullable=False)
|
ap_published_at = Column(DateTime(timezone=True), nullable=False)
|
||||||
ap_object: Mapped[ap.RawObject] = Column(JSON, nullable=False)
|
ap_object: Mapped[ap.RawObject] = Column(JSON, nullable=False)
|
||||||
|
@ -160,7 +160,7 @@ class OutboxObject(Base, BaseObject):
|
||||||
public_id = Column(String, nullable=False, index=True)
|
public_id = Column(String, nullable=False, index=True)
|
||||||
|
|
||||||
ap_type = Column(String, nullable=False, index=True)
|
ap_type = Column(String, nullable=False, index=True)
|
||||||
ap_id = Column(String, nullable=False, unique=True, index=True)
|
ap_id: Mapped[str] = Column(String, nullable=False, unique=True, index=True)
|
||||||
ap_context = Column(String, nullable=True)
|
ap_context = Column(String, nullable=True)
|
||||||
ap_object: Mapped[ap.RawObject] = Column(JSON, nullable=False)
|
ap_object: Mapped[ap.RawObject] = Column(JSON, nullable=False)
|
||||||
|
|
||||||
|
|
|
@ -77,23 +77,29 @@ def test_send_delete__reverts_side_effects(
|
||||||
|
|
||||||
# with a note that has existing replies
|
# with a note that has existing replies
|
||||||
inbox_note = setup_inbox_note(actor)
|
inbox_note = setup_inbox_note(actor)
|
||||||
inbox_note.replies_count = 1
|
# with a bogus counter
|
||||||
|
inbox_note.replies_count = 5
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# and a local reply
|
# and 2 local replies
|
||||||
outbox_note = setup_outbox_note(
|
setup_outbox_note(
|
||||||
|
to=[ap.AS_PUBLIC],
|
||||||
|
cc=[LOCAL_ACTOR.followers_collection_id], # type: ignore
|
||||||
|
in_reply_to=inbox_note.ap_id,
|
||||||
|
)
|
||||||
|
outbox_note2 = setup_outbox_note(
|
||||||
to=[ap.AS_PUBLIC],
|
to=[ap.AS_PUBLIC],
|
||||||
cc=[LOCAL_ACTOR.followers_collection_id], # type: ignore
|
cc=[LOCAL_ACTOR.followers_collection_id], # type: ignore
|
||||||
in_reply_to=inbox_note.ap_id,
|
in_reply_to=inbox_note.ap_id,
|
||||||
)
|
)
|
||||||
inbox_note.replies_count = inbox_note.replies_count + 1
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
# When deleting one of the replies
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/admin/actions/delete",
|
"/admin/actions/delete",
|
||||||
data={
|
data={
|
||||||
"redirect_url": "http://testserver/",
|
"redirect_url": "http://testserver/",
|
||||||
"ap_object_id": outbox_note.ap_id,
|
"ap_object_id": outbox_note2.ap_id,
|
||||||
"csrf_token": generate_csrf_token(),
|
"csrf_token": generate_csrf_token(),
|
||||||
},
|
},
|
||||||
cookies=generate_admin_session_cookies(),
|
cookies=generate_admin_session_cookies(),
|
||||||
|
@ -108,14 +114,14 @@ def test_send_delete__reverts_side_effects(
|
||||||
select(models.OutboxObject).where(models.OutboxObject.ap_type == "Delete")
|
select(models.OutboxObject).where(models.OutboxObject.ap_type == "Delete")
|
||||||
).scalar_one()
|
).scalar_one()
|
||||||
assert outbox_object.ap_type == "Delete"
|
assert outbox_object.ap_type == "Delete"
|
||||||
assert outbox_object.activity_object_ap_id == outbox_note.ap_id
|
assert outbox_object.activity_object_ap_id == outbox_note2.ap_id
|
||||||
|
|
||||||
# And an outgoing activity was queued
|
# And an outgoing activity was queued
|
||||||
outgoing_activity = db.execute(select(models.OutgoingActivity)).scalar_one()
|
outgoing_activity = db.execute(select(models.OutgoingActivity)).scalar_one()
|
||||||
assert outgoing_activity.outbox_object_id == outbox_object.id
|
assert outgoing_activity.outbox_object_id == outbox_object.id
|
||||||
assert outgoing_activity.recipient == ra.inbox_url
|
assert outgoing_activity.recipient == ra.inbox_url
|
||||||
|
|
||||||
# And the replies count of the replied object was decremented
|
# And the replies count of the replied object was refreshed correctly
|
||||||
db.refresh(inbox_note)
|
db.refresh(inbox_note)
|
||||||
assert inbox_note.replies_count == 1
|
assert inbox_note.replies_count == 1
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue