feat(broker+api): every mesh ships with a default #general topic
The web chat surface needed a guaranteed landing room — a topic that exists for every mesh from creation onward so the dashboard always has somewhere to drop the user. #general is the convention; ephemeral DMs remain ephemeral (mesh.message_queue) so agentic privacy is unchanged. Three hooks plus a backfill: - packages/api/src/modules/mesh/mutations.ts — createMyMesh now calls ensureGeneralTopic() right after the mesh insert. New helper is idempotent via the unique (mesh_id, name) index. - apps/broker/src/index.ts — handleMeshCreate (CLI claudemesh new) inserts #general + subscribes the owner member as 'lead' in the same handler. - apps/broker/src/crypto.ts — invite-claim flow auto-subscribes the newly minted member to #general as 'member', defensively ensuring the topic exists if predates this change. - packages/db/migrations/0024_general_topic_backfill.sql — one-shot backfill: creates #general for every active mesh that doesn't have one, subscribes every active member, and marks the mesh owner as 'lead' based on owner_user_id == member.user_id. Idempotent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
import { and, eq, isNull, lt, sql } from "drizzle-orm";
|
||||
import sodium from "libsodium-wrappers";
|
||||
import { db } from "./db";
|
||||
import { invite as inviteTable, mesh, meshMember } from "@turbostarter/db/schema/mesh";
|
||||
import { invite as inviteTable, mesh, meshMember, meshTopic, meshTopicMember } from "@turbostarter/db/schema/mesh";
|
||||
|
||||
let ready = false;
|
||||
async function ensureSodium(): Promise<typeof sodium> {
|
||||
@@ -344,6 +344,32 @@ export async function claimInviteV2Core(params: {
|
||||
return { ok: false, status: 400, body: { error: "malformed" } };
|
||||
}
|
||||
|
||||
// 6b. Auto-subscribe the new member to #general (the default mesh-wide
|
||||
// room). Idempotent via unique (topic_id, member_id). If the mesh was
|
||||
// created before #general auto-creation existed, ensure it now via a
|
||||
// best-effort INSERT … ON CONFLICT — backfill migration handles the
|
||||
// bulk case so this is just a safety net.
|
||||
await db
|
||||
.insert(meshTopic)
|
||||
.values({
|
||||
meshId: inv.meshId,
|
||||
name: "general",
|
||||
description: "Default mesh-wide channel. Every member can read and post.",
|
||||
visibility: "public",
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
const [generalTopic] = await db
|
||||
.select({ id: meshTopic.id })
|
||||
.from(meshTopic)
|
||||
.where(and(eq(meshTopic.meshId, inv.meshId), eq(meshTopic.name, "general")))
|
||||
.limit(1);
|
||||
if (generalTopic) {
|
||||
await db
|
||||
.insert(meshTopicMember)
|
||||
.values({ topicId: generalTopic.id, memberId: row.id, role: "member" })
|
||||
.onConflictDoNothing();
|
||||
}
|
||||
|
||||
// 7. Seal the mesh root_key to the recipient's x25519 pubkey.
|
||||
let sealed: string;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user