Compare commits
1 Commits
b16ba34b40
...
f085d4a8d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f085d4a8d0 |
@@ -262,6 +262,10 @@ class FederationService(Protocol):
|
|||||||
self, session: AsyncSession, acct: str,
|
self, session: AsyncSession, acct: str,
|
||||||
) -> RemoteActorDTO | None: ...
|
) -> RemoteActorDTO | None: ...
|
||||||
|
|
||||||
|
async def search_actors(
|
||||||
|
self, session: AsyncSession, query: str, page: int = 1, limit: int = 20,
|
||||||
|
) -> tuple[list[RemoteActorDTO], int]: ...
|
||||||
|
|
||||||
# -- Following (outbound) -------------------------------------------------
|
# -- Following (outbound) -------------------------------------------------
|
||||||
async def send_follow(
|
async def send_follow(
|
||||||
self, session: AsyncSession, local_username: str, remote_actor_url: str,
|
self, session: AsyncSession, local_username: str, remote_actor_url: str,
|
||||||
|
|||||||
@@ -541,6 +541,92 @@ class SqlFederationService:
|
|||||||
|
|
||||||
return await self._upsert_remote_actor(session, actor_url, data)
|
return await self._upsert_remote_actor(session, actor_url, data)
|
||||||
|
|
||||||
|
async def search_actors(
|
||||||
|
self, session: AsyncSession, query: str, page: int = 1, limit: int = 20,
|
||||||
|
) -> tuple[list[RemoteActorDTO], int]:
|
||||||
|
from sqlalchemy import or_
|
||||||
|
|
||||||
|
pattern = f"%{query}%"
|
||||||
|
offset = (page - 1) * limit
|
||||||
|
|
||||||
|
# WebFinger resolve for @user@domain queries (first page only)
|
||||||
|
webfinger_result: RemoteActorDTO | None = None
|
||||||
|
if page == 1 and "@" in query:
|
||||||
|
webfinger_result = await self.search_remote_actor(session, query)
|
||||||
|
|
||||||
|
# Search cached remote actors
|
||||||
|
remote_filter = or_(
|
||||||
|
RemoteActor.preferred_username.ilike(pattern),
|
||||||
|
RemoteActor.display_name.ilike(pattern),
|
||||||
|
RemoteActor.domain.ilike(pattern),
|
||||||
|
)
|
||||||
|
remote_total = (
|
||||||
|
await session.execute(
|
||||||
|
select(func.count(RemoteActor.id)).where(remote_filter)
|
||||||
|
)
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
# Search local actor profiles
|
||||||
|
local_filter = or_(
|
||||||
|
ActorProfile.preferred_username.ilike(pattern),
|
||||||
|
ActorProfile.display_name.ilike(pattern),
|
||||||
|
)
|
||||||
|
local_total = (
|
||||||
|
await session.execute(
|
||||||
|
select(func.count(ActorProfile.id)).where(local_filter)
|
||||||
|
)
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
total = remote_total + local_total
|
||||||
|
|
||||||
|
# Fetch remote actors page
|
||||||
|
remote_rows = (
|
||||||
|
await session.execute(
|
||||||
|
select(RemoteActor)
|
||||||
|
.where(remote_filter)
|
||||||
|
.order_by(RemoteActor.preferred_username)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
)
|
||||||
|
).scalars().all()
|
||||||
|
|
||||||
|
results: list[RemoteActorDTO] = [_remote_actor_to_dto(r) for r in remote_rows]
|
||||||
|
|
||||||
|
# Fill remaining slots with local actors
|
||||||
|
remaining = limit - len(results)
|
||||||
|
local_offset = max(0, offset - remote_total)
|
||||||
|
if remaining > 0 and offset + len(results) >= remote_total:
|
||||||
|
domain = _domain()
|
||||||
|
local_rows = (
|
||||||
|
await session.execute(
|
||||||
|
select(ActorProfile)
|
||||||
|
.where(local_filter)
|
||||||
|
.order_by(ActorProfile.preferred_username)
|
||||||
|
.limit(remaining)
|
||||||
|
.offset(local_offset)
|
||||||
|
)
|
||||||
|
).scalars().all()
|
||||||
|
for lp in local_rows:
|
||||||
|
results.append(RemoteActorDTO(
|
||||||
|
id=0,
|
||||||
|
actor_url=f"https://{domain}/users/{lp.preferred_username}",
|
||||||
|
inbox_url=f"https://{domain}/users/{lp.preferred_username}/inbox",
|
||||||
|
preferred_username=lp.preferred_username,
|
||||||
|
domain=domain,
|
||||||
|
display_name=lp.display_name,
|
||||||
|
summary=lp.summary,
|
||||||
|
icon_url=None,
|
||||||
|
))
|
||||||
|
|
||||||
|
# Prepend WebFinger result (deduped)
|
||||||
|
if webfinger_result:
|
||||||
|
existing_urls = {r.actor_url for r in results}
|
||||||
|
if webfinger_result.actor_url not in existing_urls:
|
||||||
|
results.insert(0, webfinger_result)
|
||||||
|
total += 1
|
||||||
|
|
||||||
|
return results, total
|
||||||
|
|
||||||
# -- Following (outbound) -------------------------------------------------
|
# -- Following (outbound) -------------------------------------------------
|
||||||
|
|
||||||
async def send_follow(
|
async def send_follow(
|
||||||
|
|||||||
@@ -246,6 +246,9 @@ class StubFederationService:
|
|||||||
async def search_remote_actor(self, session, acct):
|
async def search_remote_actor(self, session, acct):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
async def search_actors(self, session, query, page=1, limit=20):
|
||||||
|
return [], 0
|
||||||
|
|
||||||
async def send_follow(self, session, local_username, remote_actor_url):
|
async def send_follow(self, session, local_username, remote_actor_url):
|
||||||
raise RuntimeError("FederationService not available")
|
raise RuntimeError("FederationService not available")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user