feat!: remove direct messaging (chat)

E2EE direct messages, the chat dock, the Messages module, the message
WebSocket and its realtime registry, and the messages / message_keys
tables (migration 0097; the downgrade restores the empty schema). Prod
held two welcome notes, no message between people and one key — never
used (owner decision, E34). WebSocket now has no owner module: the lint
bans it everywhere. Privacy policy updated: it no longer promises E2EE
for a feature that is gone.
This commit is contained in:
2026-10-05 00:19:38 +02:00
parent 7f08c510bf
commit cca3315836
71 changed files with 122 additions and 3011 deletions
-1
View File
@@ -30,7 +30,6 @@ const SLICE_MODULES = [
["quotaApi", () => import("./quota")],
["adminApi", () => import("./admin")],
["plexApi", () => import("./plex")],
["messagingApi", () => import("./messaging")],
["downloadsApi", () => import("./downloads")],
["diagnosticsApi", () => import("./diagnostics")],
["debugSessionsApi", () => import("./diagnostics")],
-72
View File
@@ -1,72 +0,0 @@
// Direct-messaging domain: the E2EE key bundle setup/reset, the conversation list, threads, and
// send/unread. Self-contained — types reference only each other, methods only touch core's req.
// (The WebCrypto layer that encrypts/decrypts message bodies lives in e2ee.ts, not here.)
import { req } from "./core";
export interface MessageUser {
id: number;
name: string;
avatar_url: string | null;
}
export interface Message {
id: number;
kind: "user" | "system";
sender_id: number | null;
recipient_id: number;
body: string | null; // plaintext, system messages only
ciphertext: string | null; // base64, E2EE user messages only
iv: string | null;
read_at: string | null;
created_at: string | null;
}
export interface Conversation {
partner: MessageUser;
last_message: Message;
unread: number;
}
// My own E2EE key bundle. When configured, the wrapped blob + params let a device unlock.
export interface MyKeyBundle {
configured: boolean;
public_key?: string;
wrapped_private_key?: string;
salt?: string;
wrap_iv?: string;
key_check?: string;
}
export interface KeySetup {
public_key: string;
wrapped_private_key: string;
salt: string;
wrap_iv: string;
key_check: string;
}
export const messagingApi = {
messageMyKey: (): Promise<MyKeyBundle> => req("/api/messages/keys/me"),
setupMessageKey: (k: KeySetup): Promise<{ configured: boolean }> =>
req("/api/messages/keys", { method: "POST", body: JSON.stringify(k) }),
// Forgotten-passphrase reset: drops the server key (and the now-undecryptable history) so a new
// passphrase can be set. Password accounts must pass their current password; errors shown inline.
resetMessageKey: (currentPassword?: string): Promise<{ configured: boolean }> =>
req(
"/api/messages/keys/reset",
{ method: "POST", body: JSON.stringify({ current_password: currentPassword ?? null }) },
{ quiet: true },
),
messagePublicKey: (userId: number): Promise<{ user_id: number; public_key: string }> =>
req(`/api/messages/keys/${userId}`),
messageUsers: (): Promise<MessageUser[]> => req("/api/messages/users"),
conversations: (): Promise<{ items: Conversation[] }> => req("/api/messages/conversations"),
thread: (partnerId: number): Promise<{ partner: MessageUser; items: Message[] }> =>
req(`/api/messages/conversations/${partnerId}`),
sendMessage: (recipientId: number, ciphertext: string, iv: string): Promise<Message> =>
req("/api/messages", {
method: "POST",
body: JSON.stringify({ recipient_id: recipientId, ciphertext, iv }),
}),
messageUnreadCount: (): Promise<{ count: number }> => req("/api/messages/unread_count"),
};