Compare commits
1 Commits
widget-pha
...
04f7c5e85c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04f7c5e85c |
@@ -221,6 +221,11 @@ class FederationService(Protocol):
|
||||
self, session: AsyncSession, username: str,
|
||||
) -> list[APFollowerDTO]: ...
|
||||
|
||||
async def get_followers_paginated(
|
||||
self, session: AsyncSession, username: str,
|
||||
page: int = 1, per_page: int = 20,
|
||||
) -> tuple[list[RemoteActorDTO], int]: ...
|
||||
|
||||
async def add_follower(
|
||||
self, session: AsyncSession, username: str,
|
||||
follower_acct: str, follower_inbox: str, follower_actor_url: str,
|
||||
@@ -283,6 +288,11 @@ class FederationService(Protocol):
|
||||
before: datetime | None = None, limit: int = 20,
|
||||
) -> list[TimelineItemDTO]: ...
|
||||
|
||||
async def get_actor_timeline(
|
||||
self, session: AsyncSession, remote_actor_id: int,
|
||||
before: datetime | None = None, limit: int = 20,
|
||||
) -> list[TimelineItemDTO]: ...
|
||||
|
||||
# -- Local posts ----------------------------------------------------------
|
||||
async def create_local_post(
|
||||
self, session: AsyncSession, actor_profile_id: int,
|
||||
|
||||
@@ -376,6 +376,65 @@ class SqlFederationService:
|
||||
)
|
||||
return result.rowcount > 0
|
||||
|
||||
async def get_followers_paginated(
|
||||
self, session: AsyncSession, username: str,
|
||||
page: int = 1, per_page: int = 20,
|
||||
) -> tuple[list[RemoteActorDTO], int]:
|
||||
actor = (
|
||||
await session.execute(
|
||||
select(ActorProfile).where(ActorProfile.preferred_username == username)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if actor is None:
|
||||
return [], 0
|
||||
|
||||
total = (
|
||||
await session.execute(
|
||||
select(func.count(APFollower.id)).where(
|
||||
APFollower.actor_profile_id == actor.id,
|
||||
)
|
||||
)
|
||||
).scalar() or 0
|
||||
|
||||
offset = (page - 1) * per_page
|
||||
followers = (
|
||||
await session.execute(
|
||||
select(APFollower)
|
||||
.where(APFollower.actor_profile_id == actor.id)
|
||||
.order_by(APFollower.created_at.desc())
|
||||
.limit(per_page)
|
||||
.offset(offset)
|
||||
)
|
||||
).scalars().all()
|
||||
|
||||
results: list[RemoteActorDTO] = []
|
||||
for f in followers:
|
||||
# Try to resolve from cached remote actors first
|
||||
remote = (
|
||||
await session.execute(
|
||||
select(RemoteActor).where(
|
||||
RemoteActor.actor_url == f.follower_actor_url,
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if remote:
|
||||
results.append(_remote_actor_to_dto(remote))
|
||||
else:
|
||||
# Synthesise a minimal DTO from follower data
|
||||
from urllib.parse import urlparse
|
||||
domain = urlparse(f.follower_actor_url).netloc
|
||||
results.append(RemoteActorDTO(
|
||||
id=0,
|
||||
actor_url=f.follower_actor_url,
|
||||
inbox_url=f.follower_inbox,
|
||||
preferred_username=f.follower_acct.split("@")[0] if "@" in f.follower_acct else f.follower_acct,
|
||||
domain=domain,
|
||||
display_name=None,
|
||||
summary=None,
|
||||
icon_url=None,
|
||||
))
|
||||
return results, total
|
||||
|
||||
# -- Remote actors --------------------------------------------------------
|
||||
|
||||
async def get_or_fetch_remote_actor(
|
||||
@@ -966,6 +1025,46 @@ class SqlFederationService:
|
||||
))
|
||||
return items
|
||||
|
||||
async def get_actor_timeline(
|
||||
self, session: AsyncSession, remote_actor_id: int,
|
||||
before: datetime | None = None, limit: int = 20,
|
||||
) -> list[TimelineItemDTO]:
|
||||
remote_actor = (
|
||||
await session.execute(
|
||||
select(RemoteActor).where(RemoteActor.id == remote_actor_id)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if not remote_actor:
|
||||
return []
|
||||
|
||||
q = (
|
||||
select(APRemotePost)
|
||||
.where(APRemotePost.remote_actor_id == remote_actor_id)
|
||||
)
|
||||
if before:
|
||||
q = q.where(APRemotePost.published < before)
|
||||
q = q.order_by(APRemotePost.published.desc()).limit(limit)
|
||||
|
||||
posts = (await session.execute(q)).scalars().all()
|
||||
return [
|
||||
TimelineItemDTO(
|
||||
id=f"remote:{p.id}",
|
||||
post_type="remote",
|
||||
content=p.content or "",
|
||||
published=p.published,
|
||||
actor_name=remote_actor.display_name or remote_actor.preferred_username,
|
||||
actor_username=remote_actor.preferred_username,
|
||||
object_id=p.object_id,
|
||||
summary=p.summary,
|
||||
url=p.url,
|
||||
actor_domain=remote_actor.domain,
|
||||
actor_icon=remote_actor.icon_url,
|
||||
actor_url=remote_actor.actor_url,
|
||||
author_inbox=remote_actor.inbox_url,
|
||||
)
|
||||
for p in posts
|
||||
]
|
||||
|
||||
# -- Local posts ----------------------------------------------------------
|
||||
|
||||
async def create_local_post(
|
||||
|
||||
@@ -239,6 +239,9 @@ class StubFederationService:
|
||||
async def get_following(self, session, username, page=1, per_page=20):
|
||||
return [], 0
|
||||
|
||||
async def get_followers_paginated(self, session, username, page=1, per_page=20):
|
||||
return [], 0
|
||||
|
||||
async def accept_follow_response(self, session, local_username, remote_actor_url):
|
||||
pass
|
||||
|
||||
@@ -260,6 +263,9 @@ class StubFederationService:
|
||||
async def get_public_timeline(self, session, before=None, limit=20):
|
||||
return []
|
||||
|
||||
async def get_actor_timeline(self, session, remote_actor_id, before=None, limit=20):
|
||||
return []
|
||||
|
||||
async def create_local_post(self, session, actor_profile_id, content, visibility="public", in_reply_to=None):
|
||||
raise RuntimeError("FederationService not available")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user