API-ключи привязаны к ботам 1:1 (этап 1)
- Миграция 0064: organization_api_keys.bot_id (unique partial), task_audit_log.bot_id, file_uploads.bot_id
- POST/PUT /api/bots: apiAccess {enabled, mode, formIds, tableIds} — ключ создаётся/обновляется/деактивируется вместе с ботом, сырой ключ показывается один раз
- POST /api/bots/:id/api-key/regenerate; GET /api/bots возвращает apiKey summary
This commit is contained in:
@@ -1714,6 +1714,14 @@ const accessPolicySchema = z.object({
|
||||
userIds: z.array(z.number()).optional(),
|
||||
});
|
||||
|
||||
// Настройки API-доступа бота (ключ organization_api_keys, привязанный к боту 1:1)
|
||||
export const botApiAccessSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
mode: z.enum(['read', 'write', 'full']).optional(),
|
||||
formIds: z.array(z.number().int()).nullable().optional(),
|
||||
tableIds: z.array(z.number().int()).nullable().optional(),
|
||||
});
|
||||
|
||||
// Create/Update schemas for bots
|
||||
export const createBotSchema = z.object({
|
||||
name: z.string().min(1, "Название бота обязательно").max(200),
|
||||
@@ -1734,6 +1742,7 @@ export const createBotSchema = z.object({
|
||||
sendSystemPrompt: z.boolean().optional(),
|
||||
sendCardInfo: z.boolean().optional(),
|
||||
sendChatHistory: z.boolean().optional(),
|
||||
apiAccess: botApiAccessSchema.optional(),
|
||||
});
|
||||
|
||||
export const updateBotSchema = z.object({
|
||||
@@ -1755,6 +1764,7 @@ export const updateBotSchema = z.object({
|
||||
sendSystemPrompt: z.boolean().optional(),
|
||||
sendCardInfo: z.boolean().optional(),
|
||||
sendChatHistory: z.boolean().optional(),
|
||||
apiAccess: botApiAccessSchema.optional(),
|
||||
}).partial();
|
||||
|
||||
export const createBotSubscriptionSchema = z.object({
|
||||
@@ -2577,9 +2587,13 @@ export const organizationApiKeys = pgTable("organization_api_keys", {
|
||||
isActive: boolean("is_active").notNull().default(true),
|
||||
isLegacy: boolean("is_legacy").notNull().default(false),
|
||||
scopes: jsonb("scopes").$type<ApiKeyScopes>(),
|
||||
// Привязка ключа к боту (1:1); NULL — обычный/legacy-ключ без бота
|
||||
botId: integer("bot_id").references(() => bots.id),
|
||||
}, (table) => ({
|
||||
orgIndex: index("api_keys_org_idx").on(table.organizationId),
|
||||
hashIndex: index("api_keys_hash_idx").on(table.keyHash),
|
||||
// Один ключ на бота (частичный индекс — NULL не ограничивается)
|
||||
botUniqueIdx: uniqueIndex("organization_api_keys_bot_id_unique").on(table.botId).where(sql`${table.botId} IS NOT NULL`),
|
||||
}));
|
||||
|
||||
export type OrganizationApiKey = typeof organizationApiKeys.$inferSelect;
|
||||
@@ -2712,6 +2726,8 @@ export const taskAuditLog = pgTable("task_audit_log", {
|
||||
newValue: jsonb("new_value"),
|
||||
changedBy: integer("changed_by").references(() => users.id),
|
||||
changedByName: text("changed_by_name"),
|
||||
// Атрибуция действий бота (действие выполнено по API-ключу бота); NULL — действие пользователя
|
||||
botId: integer("bot_id"),
|
||||
metadata: jsonb("metadata"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
}, (table) => ({
|
||||
@@ -3192,6 +3208,8 @@ export const fileUploads = pgTable("file_uploads", {
|
||||
sizeBytes: integer("size_bytes"),
|
||||
taskId: integer("task_id"),
|
||||
fieldId: integer("field_id"),
|
||||
// Атрибуция загрузки бота (файл загружен по API-ключу бота); NULL — загрузка пользователя
|
||||
botId: integer("bot_id"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
}, (table) => ({
|
||||
orgIdx: index("file_uploads_org_idx").on(table.organizationId),
|
||||
|
||||
Reference in New Issue
Block a user