feat(broker): add cli-sync, member-api, jwt modules + DB schema updates
Some checks failed
CI / Typecheck (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

New broker endpoints for CLI auth sync flow (POST /cli-sync),
member profile management, and mesh settings. Includes JWT
verification for dashboard-issued sync tokens. DB schema adds
member profile fields and mesh policy columns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-09 01:54:50 +01:00
parent d263fe0f26
commit a7d9ecab15
6 changed files with 605 additions and 1 deletions

View File

@@ -0,0 +1,12 @@
-- Member profile columns: roleTag, defaultGroups, messageMode, dashboardUserId
ALTER TABLE "mesh"."member" ADD COLUMN "role_tag" text;--> statement-breakpoint
ALTER TABLE "mesh"."member" ADD COLUMN "default_groups" jsonb DEFAULT '[]'::jsonb;--> statement-breakpoint
ALTER TABLE "mesh"."member" ADD COLUMN "message_mode" text DEFAULT 'push';--> statement-breakpoint
ALTER TABLE "mesh"."member" ADD COLUMN "dashboard_user_id" text;--> statement-breakpoint
CREATE INDEX "member_dashboard_user_idx" ON "mesh"."member" ("dashboard_user_id");--> statement-breakpoint
-- Mesh policy: selfEditable (which profile fields members can self-edit)
ALTER TABLE "mesh"."mesh" ADD COLUMN "self_editable" jsonb DEFAULT '{"displayName":true,"roleTag":true,"groups":true,"messageMode":true}'::jsonb;--> statement-breakpoint
-- Invite preset: pre-configured profile values applied to new members on join
ALTER TABLE "mesh"."invite" ADD COLUMN "preset" jsonb DEFAULT '{}'::jsonb;

View File

@@ -1,6 +1,7 @@
import { relations } from "drizzle-orm";
import {
boolean,
index,
integer,
jsonb,
pgSchema,
@@ -108,6 +109,16 @@ export const mesh = meshSchema.table("mesh", {
* with recipient's ed25519 pubkey).
*/
rootKey: text(),
/**
* Per-mesh policy controlling which profile fields members can edit
* about themselves. Admins can always edit anyone's profile regardless.
*/
selfEditable: jsonb().$type<{
displayName: boolean;
roleTag: boolean;
groups: boolean;
messageMode: boolean;
}>().default({ displayName: true, roleTag: true, groups: true, messageMode: true }),
createdAt: timestamp().defaultNow().notNull(),
archivedAt: timestamp(),
});
@@ -135,10 +146,20 @@ export const meshMember = meshSchema.table("member", {
peerPubkey: text().notNull(),
displayName: text().notNull(),
role: meshRoleEnum().notNull().default("member"),
/** Free-text role label visible to peers (not to be confused with `role` which is the permission enum). */
roleTag: text(),
/** Persistent group memberships set via dashboard or CLI profile command. */
defaultGroups: jsonb().$type<Array<{ name: string; role?: string }>>().default([]),
/** Delivery preference: push (real-time), inbox (held), off (manual poll). */
messageMode: text().default("push"),
/** Links this mesh member to a dashboard OAuth user (Payload CMS user.id). */
dashboardUserId: text(),
joinedAt: timestamp().defaultNow().notNull(),
lastSeenAt: timestamp(),
revokedAt: timestamp(),
});
}, (table) => [
index("member_dashboard_user_idx").on(table.dashboardUserId),
]);
/**
* Invite tokens used to join a mesh via shareable URL.
@@ -157,6 +178,13 @@ export const invite = meshSchema.table("invite", {
maxUses: integer().notNull().default(1),
usedCount: integer().notNull().default(0),
role: meshRoleEnum().notNull().default("member"),
/** Pre-configured profile values applied to new members on join. */
preset: jsonb().$type<{
displayName?: string;
roleTag?: string;
groups?: Array<{ name: string; role?: string }>;
messageMode?: string;
}>().default({}),
expiresAt: timestamp().notNull(),
createdBy: text()
.references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" })