5.8 KiB
Server Discovery
Area: server-directory Status: Active Last updated: 2025-02-14
Overview
Server discovery lets a signed-in user find public servers to join without knowing an exact name. It spans the signaling server (REST routes + CQRS query handlers that rank public servers) and the product client (server-directory domain API/facade plus the /dashboard landing and /servers browse page). It complements the existing free-text GET /api/servers search with two curated lists — featured and trending.
Responsibilities
- Server: rank and return public servers as featured (most-populated) and trending (most-recently-active) lists, capped per request.
- Client: fetch those lists through
ServerDirectoryFacadeand render them via the reusableapp-server-browsercomponent on/serversand/dashboard. - It does NOT own: free-text search (
GET /api/servers), join/access checks (/api/servers/:id/join), invites, or room signal-affinity. Discovery is read-only browsing; joining flows through existing paths.
Key concepts
- Featured: public servers ranked by membership count descending, ties broken by most recent
lastSeen(rankFeaturedServers). - Trending: public servers ranked by most recent
lastSeendescending, ties broken by membership count (rankTrendingServers). - Discovery limit: each route clamps
limitto[1, 50](parseDiscoveryLimit), default12.
API Endpoints
Both endpoints live in server/src/routes/servers.ts and must be registered before the parameterised /:id route, otherwise Express resolves featured/trending as a server id.
GET /api/servers/featured
- Method: GET
- Authentication: None (public discovery)
- Rate Limiting: No
- Query params:
limit(optional integer; clamped to[1, 50], default12)
GET /api/servers/trending
- Method: GET
- Authentication: None (public discovery)
- Rate Limiting: No
- Query params:
limit(optional integer; clamped to[1, 50], default12)
Response Schema (both)
{
"servers": "ServerInfo[] — enriched public servers (icon, channels, sourceId/sourceName/sourceUrl filled by the client API layer)",
"total": "number — count of servers returned",
"limit": "number — the effective clamped limit"
}
ServerInfo matches the shape returned by GET /api/servers search results, so the client normalises and renders all three lists identically.
Error Responses
- 500 Internal Server Error: query handler / persistence failure.
Server internals
- Routes delegate to CQRS query handlers
handleGetFeaturedServers/handleGetTrendingServers(server/src/cqrs/queries/handlers/), dispatched viaGetFeaturedServers/GetTrendingServersquery types. - Ranking lives in
server/src/cqrs/queries/handlers/server-ranking.util.ts(rankFeaturedServers,rankTrendingServers,loadMembershipCounts). Membership counts load in a single grouped query. - Results pass through the same
enrichServer()step as search before serialisation.
Client internals
ServerDirectoryApiService.getFeaturedServers()/getTrendingServers()call the routes through a shared privategetDiscoveryServers(path)helper and normalise intoServerInfo[].ServerDirectoryService→ServerDirectoryFacadeexposegetFeaturedServers()/getTrendingServers()as the domain boundary.FindServersComponent(/servers) composes Recently active (the user's saved rooms, capped at 6), Featured, and Trending sections, all rendered throughapp-server-browserwith[showMyServers]="true".DashboardComponent(/dashboard) is a single-column landing page (max-width centered, no in-page sidebars): a header greeting (no emoji), a global search withCtrl+Kfocus and localStorage-backed Recent Searches chips shown beneath it, three primary action cards (Find People →/people, Find Servers →/servers, Create Server →/create-server— one link each), and discovery panels People you might know, Popular Servers, Your Friends, and Recently Active Servers. Each list is capped at 5 (DISCOVERY_LIMIT). It loadspopularServerson init fromgetFeaturedServers(5), falling back togetTrendingServers(5)when featured is empty; reusesapp-friend-buttonfor Add andapp-user-avatarfor people rows.peopleYouMightKnowexcludes existing friends (viaFriendService.friendIds());friendslists discovered people who are friends. "See all" header links route to the matching/peopleor/serverspage (no duplicated footer links). Recent searches are recorded on Enter (deduped, most-recent-first, capped at 8) and persisted undermetoyou_dashboard_recent_searches.- The servers-rail top button (
servers-rail.component) is the Dashboard button (lucideLayoutDashboard,title="Dashboard"); itsgoToDashboard()handler deselects any active voice server and navigates to/dashboard. Creating a server is reached via the dashboard //create-serverlink instead. - On mobile (
ViewportService.isMobile()),DashboardComponent,FindPeopleComponent(/people), andFindServersComponent(/servers) each mount their page body inside a single<swiper-container>slide next toapp-servers-rail(railshrink-0, contentflex-1with a left border), mirroring the chat-room / DM-workspace mobile layout so the primary navigation rail stays reachable. The page body is shared between the desktop and mobile branches via an<ng-template #pageContent>+[ngTemplateOutlet], and each component declaresschemas: [CUSTOM_ELEMENTS_SCHEMA]for the Swiper custom elements.
Related
- Product-client domain README:
toju-app/src/app/domains/server-directory/README.md - People discovery (
/people):toju-app/src/app/domains/direct-message/README.md