feat(api+web): unread counts per topic + PATCH /read mark-as-read
PATCH /v1/topics/:name/read upserts topic_member.last_read_at for the api key's issuing member. The chat panel calls it on mount and on every inbound SSE message (5s debounce so we don't hammer it). GET /v1/topics now returns unread per topic — counts messages newer than last_read_at and not authored by the viewer. Mesh detail page shows a clay-rounded badge next to each topic name with the count (99+ ceiling). AuthedApiKey gains issuedByMemberId so endpoints can attribute side-effects to the minting member. Required because external api keys aren't tied to a specific peer member; only dashboard- and CLI-minted keys carry one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,13 @@ export type ApiKeyCapability = "send" | "read" | "state_write" | "admin";
|
||||
export interface AuthedApiKey {
|
||||
id: string;
|
||||
meshId: string;
|
||||
/**
|
||||
* The mesh member that minted this key. Dashboard keys carry the
|
||||
* owner's member id; CLI-minted keys carry the issuing peer. Endpoints
|
||||
* that attribute a side-effect to a member (e.g. PATCH /read,
|
||||
* presence ping) read this field instead of the api key id.
|
||||
*/
|
||||
issuedByMemberId: string | null;
|
||||
capabilities: ApiKeyCapability[];
|
||||
topicScopes: string[] | null;
|
||||
}
|
||||
@@ -37,6 +44,7 @@ async function verifyBearer(secret: string): Promise<AuthedApiKey | null> {
|
||||
secretHash: meshApiKey.secretHash,
|
||||
capabilities: meshApiKey.capabilities,
|
||||
topicScopes: meshApiKey.topicScopes,
|
||||
issuedByMemberId: meshApiKey.issuedByMemberId,
|
||||
revokedAt: meshApiKey.revokedAt,
|
||||
expiresAt: meshApiKey.expiresAt,
|
||||
})
|
||||
@@ -58,6 +66,7 @@ async function verifyBearer(secret: string): Promise<AuthedApiKey | null> {
|
||||
return {
|
||||
id: c.id,
|
||||
meshId: c.meshId,
|
||||
issuedByMemberId: c.issuedByMemberId ?? null,
|
||||
capabilities: (c.capabilities ?? []) as ApiKeyCapability[],
|
||||
topicScopes: c.topicScopes ?? null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user