Add permission-scoped MCP, readiness checks, and management UI improvements
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE "assistant_tokens" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
|
||||
"name" text NOT NULL,
|
||||
"token_hash" text NOT NULL UNIQUE,
|
||||
"permissions" jsonb NOT NULL,
|
||||
"read_only" boolean DEFAULT true NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"revoked_at" timestamp with time zone,
|
||||
"last_used_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
CREATE INDEX "assistant_tokens_user_idx" ON "assistant_tokens" ("user_id");
|
||||
@@ -80,6 +80,7 @@
|
||||
"breakpoints": true
|
||||
},
|
||||
{ "idx": 11, "version": "7", "when": 1789086000000, "tag": "0011_event_signs", "breakpoints": true },
|
||||
{ "idx": 12, "version": "7", "when": 1789086100000, "tag": "0012_invite_token", "breakpoints": true }
|
||||
{ "idx": 12, "version": "7", "when": 1789086100000, "tag": "0012_invite_token", "breakpoints": true },
|
||||
{ "idx": 13, "version": "7", "when": 1789164000000, "tag": "0013_assistant_tokens", "breakpoints": true }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { getDb } from "./db";
|
||||
|
||||
// Bound probes and share an in-flight query so a DB outage cannot fill the pool.
|
||||
let pending: Promise<void> | undefined;
|
||||
export async function databaseReady() {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
pending ??= getDb().execute(sql`select 1`).then(() => {}).finally(() => { pending = undefined; });
|
||||
await Promise.race([pending, new Promise<never>((_, reject) => {
|
||||
timer = setTimeout(() => reject(new Error("Database probe timed out")), 2500);
|
||||
})]);
|
||||
return true;
|
||||
} catch { return false; }
|
||||
finally { clearTimeout(timer); }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { closeDb, getDb, type Database } from "./db";
|
||||
export { databaseReady } from "./health";
|
||||
export * from "./schema";
|
||||
export * from "./auth-schema";
|
||||
|
||||
@@ -25,6 +25,19 @@ const timestamps = {
|
||||
.notNull(),
|
||||
};
|
||||
|
||||
export const assistantTokens = pgTable("assistant_tokens", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
tokenHash: text("token_hash").notNull().unique(),
|
||||
permissions: jsonb("permissions").$type<string[]>().notNull(),
|
||||
readOnly: boolean("read_only").notNull().default(true),
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
||||
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
||||
...timestamps,
|
||||
}, table => [index("assistant_tokens_user_idx").on(table.userId)]);
|
||||
|
||||
export const eventStatus = pgEnum("event_status", [
|
||||
"draft",
|
||||
"published",
|
||||
|
||||
Reference in New Issue
Block a user