mirror of
https://github.com/temetro/temetro.git
synced 2026-08-11 02:57:55 +00:00
feat(ai): clinic-wide AI kill-switch (admin-controlled)
- new org_ai_policy table + GET/PUT /api/ai/policy (read for any member, write owner/admin only); migration 0018 - /api/chat hard-blocks (403) when AI is off for the caller - Settings → AI "Availability" section: enable AI, or disable for employees only (owners/admins keep access); read-only for non-admins - sidebar, command palette and route guard hide/redirect the AI chat when it's disabled for the current user Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
import { db } from "../../db/index.js";
|
||||
import { orgAiPolicy } from "../../db/schema/org-ai-policy.js";
|
||||
|
||||
export type AiPolicy = {
|
||||
aiEnabled: boolean;
|
||||
disabledForEmployees: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_POLICY: AiPolicy = {
|
||||
aiEnabled: true,
|
||||
disabledForEmployees: false,
|
||||
};
|
||||
|
||||
// Read a clinic's AI policy, falling back to the permissive default when no row
|
||||
// exists yet.
|
||||
export async function getPolicy(orgId: string): Promise<AiPolicy> {
|
||||
const [row] = await db
|
||||
.select({
|
||||
aiEnabled: orgAiPolicy.aiEnabled,
|
||||
disabledForEmployees: orgAiPolicy.disabledForEmployees,
|
||||
})
|
||||
.from(orgAiPolicy)
|
||||
.where(eq(orgAiPolicy.organizationId, orgId))
|
||||
.limit(1);
|
||||
return row ?? DEFAULT_POLICY;
|
||||
}
|
||||
|
||||
export async function savePolicy(
|
||||
orgId: string,
|
||||
policy: AiPolicy,
|
||||
): Promise<AiPolicy> {
|
||||
await db
|
||||
.insert(orgAiPolicy)
|
||||
.values({ organizationId: orgId, ...policy })
|
||||
.onConflictDoUpdate({
|
||||
target: orgAiPolicy.organizationId,
|
||||
set: { ...policy, updatedAt: new Date() },
|
||||
});
|
||||
return policy;
|
||||
}
|
||||
|
||||
// Whether a member with `role` may use the AI under `policy`. Owners/admins keep
|
||||
// access when AI is only disabled for employees; a full disable blocks everyone.
|
||||
export function aiAllowedFor(policy: AiPolicy, role: string | undefined): boolean {
|
||||
if (!policy.aiEnabled) return false;
|
||||
if (!policy.disabledForEmployees) return true;
|
||||
const names = String(role ?? "")
|
||||
.split(",")
|
||||
.map((s) => s.trim());
|
||||
return names.some((r) => r === "owner" || r === "admin");
|
||||
}
|
||||
Reference in New Issue
Block a user