import { and, eq } from "drizzle-orm"; import { pluginPermissionGrants } from "../db/schema.js"; import type { DatabaseContext } from "./database-context.js"; import { rowsAffected } from "./mutation-result.js"; import { insertReturning } from "./returning.js"; export type PluginPermissionGrantRecord = typeof pluginPermissionGrants.$inferSelect; export interface PluginPermissionGrantCreateInput { pluginId: string; capability: string; /** Null for a bundled grant, which no user made. */ grantedBy: string | null; source?: "admin" | "bundled"; } export class PluginPermissionGrantRepository { constructor( private readonly context: DatabaseContext, private readonly onWrite?: () => void | Promise, ) {} async listByPlugin(pluginId: string): Promise { return this.context.drizzle .select() .from(pluginPermissionGrants) .where(eq(pluginPermissionGrants.pluginId, pluginId)); } async findGrant( pluginId: string, capability: string, ): Promise { const rows = await this.context.drizzle .select() .from(pluginPermissionGrants) .where( and( eq(pluginPermissionGrants.pluginId, pluginId), eq(pluginPermissionGrants.capability, capability), ), ) .limit(1); return rows[0] ?? null; } async grant( input: PluginPermissionGrantCreateInput, now = new Date().toISOString(), ): Promise { const [created] = await insertReturning( this.context, pluginPermissionGrants, { pluginId: input.pluginId, capability: input.capability, grantedBy: input.grantedBy, source: input.source ?? "admin", grantedAt: now, }, ); await this.afterWrite(); return created; } async revoke(pluginId: string, capability: string): Promise { const result = await this.context.drizzle .delete(pluginPermissionGrants) .where( and( eq(pluginPermissionGrants.pluginId, pluginId), eq(pluginPermissionGrants.capability, capability), ), ); const affected = rowsAffected(result) > 0; if (affected) await this.afterWrite(); return affected; } async deleteByPlugin(pluginId: string): Promise { const result = await this.context.drizzle .delete(pluginPermissionGrants) .where(eq(pluginPermissionGrants.pluginId, pluginId)); if (rowsAffected(result) > 0) { await this.afterWrite(); } return rowsAffected(result); } private async afterWrite(): Promise { await this.onWrite?.(); } }