soulseek: peer menu with browse and favorites

slskd has no favorites or buddy-list concept — 0.26.0's UsersController exposes
only endpoint/browse/directory/info/status — so Officer owns that data itself:
a soulseek_favorites table served by the slskd sidecar under a /_officer/*
namespace, which can never collide with slskd's /api/v0/*. The main server
gains exactly one line, injecting X-Officer-User on the proxy hop, so it stays
a thin auth proxy and grows no Soulseek logic. The route is handled before the
upstream check, so favorites keep working with slskd down.

Usernames in search results and downloads become a dropdown (browse shares,
toggle favorite). Browsing publishes to a nonce-stamped, consumed-once channel
so the Users section looks the peer up without re-running the expensive browse
on every remount, and favorites get their own section at the top of that panel,
which doubles as its landing content. CardHeader had to split its toggle row to
host the dropdown, since a trigger can't live inside the collapse button.

The schema file is deliberately self-contained so it can move wholesale into
the sidecar directory when sidecars start owning their own schema. Its DDL was
applied by hand, matching drizzle's constraint naming, rather than running a
whole-schema push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 23:36:37 +00:00
co-authored by Claude Opus 4.8
parent f777ed4197
commit b00608f3b3
14 changed files with 676 additions and 256 deletions
+7 -3
View File
@@ -6,9 +6,10 @@ import { getSlskdServerUrl } from './sidecar-server';
// the subpath + query + body to the officer-slskd sidecar, which OWNS the slskd contract and injects the
// slskd API key. The platform holds no slskd credentials.
//
// This is a catch-all with no routes of its own: the full /api/slskd/* contract (slskd's own API, e.g.
// /api/v0/searches, /api/v0/transfers) is documented at the top of the sidecar's fetch handler —
// src/servers/sidecar/slskd/index.ts.
// This is a catch-all with no routes of its own: the full /api/slskd/* contract is documented at the top
// of the sidecar's fetch handler (src/servers/sidecar/slskd/index.ts). That contract covers both slskd's
// own API (/api/v0/searches, /api/v0/transfers, …) and the sidecar's Officer-owned routes (/_officer/*,
// features slskd has no concept of). Both are opaque from here — this file never grows Soulseek logic.
export const slskdRouter = createRouter();
@@ -28,6 +29,9 @@ slskdRouter.all('/*', async (ctx) => {
if (contentType) headers['Content-Type'] = contentType;
const range = ctx.req.header('range');
if (range) headers['Range'] = range;
// Forward the authenticated user id so the sidecar can serve its own Officer-owned routes (/_officer/*,
// e.g. favourite peers) against Postgres. The sidecar binds loopback only, so this header is trusted.
headers['X-Officer-User'] = String(ctx.get('user').id);
const hasBody = method !== 'GET' && method !== 'HEAD';