feat: dashboard
This commit is contained in:
@@ -8,7 +8,7 @@ It must stay accurate as new features are introduced, renamed, merged, or remove
|
||||
|
||||
## Feature list (alphabetical)
|
||||
|
||||
_No cross-context feature docs have been written yet._
|
||||
- [Server Discovery](features/server-discovery.md) — featured/trending public-server REST endpoints (server) consumed by the `/dashboard` and `/servers` client pages.
|
||||
|
||||
The product client already documents its bounded contexts at `toju-app/src/app/domains/<name>/README.md` (Access Control, Attachment, Authentication, Chat, Direct Call, Direct Message, Experimental Media, Game Activity, Notifications, Plugins, Profile Avatar, Screen Share, Server Directory, Theme, Voice Connection, Voice Session). Those domain READMEs cover internal product-client behavior.
|
||||
|
||||
|
||||
79
agents-docs/features/server-discovery.md
Normal file
79
agents-docs/features/server-discovery.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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 `ServerDirectoryFacade` and render them via the reusable `app-server-browser` component on `/servers` and `/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 `lastSeen` descending, ties broken by membership count (`rankTrendingServers`).
|
||||
- **Discovery limit**: each route clamps `limit` to `[1, 50]` (`parseDiscoveryLimit`), default `12`.
|
||||
|
||||
---
|
||||
|
||||
## 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]`, default `12`)
|
||||
|
||||
### `GET /api/servers/trending`
|
||||
|
||||
- **Method**: GET
|
||||
- **Authentication**: None (public discovery)
|
||||
- **Rate Limiting**: No
|
||||
- **Query params**: `limit` (optional integer; clamped to `[1, 50]`, default `12`)
|
||||
|
||||
### Response Schema (both)
|
||||
|
||||
```json
|
||||
{
|
||||
"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 via `GetFeaturedServers` / `GetTrendingServers` query 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 private `getDiscoveryServers(path)` helper and normalise into `ServerInfo[]`.
|
||||
- `ServerDirectoryService` → `ServerDirectoryFacade` expose `getFeaturedServers()` / `getTrendingServers()` as the domain boundary.
|
||||
- `FindServersComponent` (`/servers`) composes **Recently active** (the user's saved rooms, capped at 6), **Featured**, and **Trending** sections, all rendered through `app-server-browser` with `[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 with `Ctrl+K` focus 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 loads `popularServers` on init from `getFeaturedServers(5)`, falling back to `getTrendingServers(5)` when featured is empty; reuses `app-friend-button` for Add and `app-user-avatar` for people rows. `peopleYouMightKnow` excludes existing friends (via `FriendService.friendIds()`); `friends` lists discovered people who are friends. "See all" header links route to the matching `/people` or `/servers` page (no duplicated footer links). Recent searches are recorded on Enter (deduped, most-recent-first, capped at 8) and persisted under `metoyou_dashboard_recent_searches`.
|
||||
- The servers-rail top button (`servers-rail.component`) is the **Dashboard** button (`lucideLayoutDashboard`, `title="Dashboard"`); its `goToDashboard()` handler deselects any active voice server and navigates to `/dashboard`. Creating a server is reached via the dashboard / `/create-server` link instead.
|
||||
- On mobile (`ViewportService.isMobile()`), `DashboardComponent`, `FindPeopleComponent` (`/people`), and `FindServersComponent` (`/servers`) each mount their page body inside a single `<swiper-container>` slide next to `app-servers-rail` (rail `shrink-0`, content `flex-1` with 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 declares `schemas: [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`
|
||||
Reference in New Issue
Block a user