6770bc23cc
Test-first from here on (per request after Phase 2): every module below was written test-then-implementation, confirmed red before green. api: accounts/sessions/chat_messages tables, plus servers.anonymousChatAllowed. Online accounts merge into one global identity per real Mojang uuid (partial unique index on mc_uuid WHERE server_id IS NULL); offline accounts are scoped per-server (partial unique index on (server_id, mc_uuid)) — see schema.ts's accounts comment and link.test.ts's merge-scoping tests. link.ts: storeLinkCode/redeemLinkCode (single-use, Redis-backed with a 10-minute TTL) and session lookup/revocation. Sessions come back in the HTTP response body rather than an httpOnly cookie — a deliberate MVP simplification (see link.ts's doc comment) that sidesteps needing to verify exactly how Elysia's .ws() routes surface cookies; the client sends the token back via X-MCMapper-Session. chat.ts/chat-gateway.ts: mod-originated chat (ws-gateway.ts's new "chat" and "link_request" message types) and browser-originated chat (/ws/chat/:serverId) both persist to chat_messages and publish to a per-server Redis pub/sub channel; browser chat additionally resolves identity (linked session > nickname > rejected if anonymous chat is disabled for that server) and forwards to the mod's own connection via a new serverId->socket registry in ws-gateway.ts (getModSocket). frontend: chat panel + link-code entry on the 2D map page, session token kept in localStorage (matching the no-cookie tradeoff above). Verified end-to-end against live containers, including through the real mod-side Java client: a link code generated by DefaultBackendConnection round-trips through actual HTTP redemption to the correct account, and a browser chat message correctly forwards through to the mod's live ChatListener callback (not just persisted/published).
33 lines
1.4 KiB
SQL
33 lines
1.4 KiB
SQL
ALTER TABLE "servers" ADD COLUMN IF NOT EXISTS "anonymous_chat_allowed" boolean NOT NULL DEFAULT true;
|
|
|
|
CREATE TABLE IF NOT EXISTS "accounts" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
"mc_uuid" text NOT NULL,
|
|
"username" text NOT NULL,
|
|
"auth_mode" text NOT NULL,
|
|
"server_id" uuid REFERENCES "servers"("id") ON DELETE CASCADE,
|
|
"created_at" timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Online accounts merge into one global identity per real Mojang UUID.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "accounts_online_uuid" ON "accounts" ("mc_uuid") WHERE "server_id" IS NULL;
|
|
-- Offline accounts are scoped to the one server that issued the uuid hash.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "accounts_offline_server_uuid" ON "accounts" ("server_id", "mc_uuid") WHERE "server_id" IS NOT NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS "sessions" (
|
|
"token" text PRIMARY KEY NOT NULL,
|
|
"account_id" uuid NOT NULL REFERENCES "accounts"("id") ON DELETE CASCADE,
|
|
"created_at" timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS "chat_messages" (
|
|
"id" bigserial PRIMARY KEY NOT NULL,
|
|
"server_id" uuid NOT NULL REFERENCES "servers"("id") ON DELETE CASCADE,
|
|
"username" text NOT NULL,
|
|
"message" text NOT NULL,
|
|
"source" text NOT NULL,
|
|
"created_at" timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS "chat_messages_server_created_idx" ON "chat_messages" ("server_id", "created_at");
|