diff --git a/app/components/TopNav.vue b/app/components/TopNav.vue index 3cd6e29..0929731 100644 --- a/app/components/TopNav.vue +++ b/app/components/TopNav.vue @@ -79,7 +79,8 @@ watch(() => route.path, () => { 注册 diff --git a/app/pages/admin.vue b/app/pages/admin.vue index 2f54a53..ba41561 100644 --- a/app/pages/admin.vue +++ b/app/pages/admin.vue @@ -25,7 +25,6 @@ const adminNav: NavItem[] = [ children: [ { label: '全局配置', to: '/admin/global-config' }, { label: '用户管理', to: '/admin/users' }, - { label: '模型配置', to: '/admin/llm-config' }, ] }, ] diff --git a/app/pages/settings.vue b/app/pages/settings.vue new file mode 100644 index 0000000..cfc84a2 --- /dev/null +++ b/app/pages/settings.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/app/pages/admin/llm-config/index.vue b/app/pages/settings/llm-config/index.vue similarity index 97% rename from app/pages/admin/llm-config/index.vue rename to app/pages/settings/llm-config/index.vue index 81830ab..ce48700 100644 --- a/app/pages/admin/llm-config/index.vue +++ b/app/pages/settings/llm-config/index.vue @@ -29,7 +29,7 @@ const searchQuery = ref('') const currentPage = ref(1) const pageSize = ref(20) -const { data, refresh, pending } = await useHttpFetch('/api/admin/llm/providers', { +const { data, refresh, pending } = await useHttpFetch('/api/llm/providers', { query: computed(() => ({ page: currentPage.value, pageSize: pageSize.value, @@ -133,7 +133,7 @@ async function handleProviderSave() { providerLoading.value = true try { if (editingProvider.value) { - await $fetch(`/api/admin/llm/providers/${editingProvider.value.id}` as string, { + await $fetch(`/api/llm/providers/${editingProvider.value.id}` as string, { method: 'PUT' as const, body: { ...providerForm.value, @@ -142,7 +142,7 @@ async function handleProviderSave() { }) showToast('供应商更新成功') } else { - await $fetch('/api/admin/llm/providers', { + await $fetch('/api/llm/providers', { method: 'POST', body: providerForm.value, }) @@ -160,7 +160,7 @@ async function handleProviderSave() { async function toggleProviderStatus(p: LlmProvider) { const newStatus = p.status === 'active' ? 'disabled' : 'active' try { - await $fetch(`/api/admin/llm/providers/${p.id}` as string, { + await $fetch(`/api/llm/providers/${p.id}` as string, { method: 'PUT' as const, body: { status: newStatus }, }) @@ -179,7 +179,7 @@ async function loadModels(providerId: number) { expandedProviderId.value = providerId modelsLoading.value = true try { - const res = await $fetch(`/api/admin/llm/models/${providerId}`) + const res = await $fetch(`/api/llm/models/${providerId}`) providerModels.value.set(providerId, (res as any)?.data ?? []) } catch { providerModels.value.set(providerId, []) @@ -192,7 +192,7 @@ async function fetchAvailableModels(providerId: number) { availableModelsLoading.value = true availableModels.value = [] try { - const res = await $fetch(`/api/admin/llm/providers/${providerId}/models`) + const res = await $fetch(`/api/llm/providers/${providerId}/models`) const models = (res as any)?.data ?? [] if (models.length > 0) { availableModels.value = models @@ -246,13 +246,13 @@ async function handleModelSave() { modelLoading.value = true try { if (editingModel.value) { - await $fetch(`/api/admin/llm/models/detail/${editingModel.value.id}` as string, { + await $fetch(`/api/llm/models/detail/${editingModel.value.id}` as string, { method: 'PUT' as const, body: modelForm.value, }) showToast('模型更新成功') } else { - await $fetch(`/api/admin/llm/models/${currentProviderId.value}` as string, { + await $fetch(`/api/llm/models/${currentProviderId.value}` as string, { method: 'POST' as const, body: modelForm.value, }) @@ -272,7 +272,7 @@ async function handleModelSave() { async function toggleModelEnabled(m: LlmModel) { const newEnabled = m.enabled ? 0 : 1 try { - await $fetch(`/api/admin/llm/models/detail/${m.id}` as string, { + await $fetch(`/api/llm/models/detail/${m.id}` as string, { method: 'PUT' as const, body: { enabled: newEnabled }, }) @@ -295,13 +295,13 @@ async function handleDelete() { deleteLoading.value = true try { if (deleteTarget.value.type === 'provider') { - await $fetch(`/api/admin/llm/providers/${deleteTarget.value.id}` as string, { method: 'DELETE' as const }) + await $fetch(`/api/llm/providers/${deleteTarget.value.id}` as string, { method: 'DELETE' as const }) if (expandedProviderId.value === deleteTarget.value.id) { expandedProviderId.value = null } await refresh() } else { - await $fetch(`/api/admin/llm/models/detail/${deleteTarget.value.id}` as string, { method: 'DELETE' as const }) + await $fetch(`/api/llm/models/detail/${deleteTarget.value.id}` as string, { method: 'DELETE' as const }) if (expandedProviderId.value) { await loadModels(expandedProviderId.value) } diff --git a/packages/drizzle-pkg/db.sqlite b/packages/drizzle-pkg/db.sqlite index bd7412c..c5eb703 100644 Binary files a/packages/drizzle-pkg/db.sqlite and b/packages/drizzle-pkg/db.sqlite differ diff --git a/packages/drizzle-pkg/lib/schema/llm.ts b/packages/drizzle-pkg/lib/schema/llm.ts index fcc33d9..a741128 100644 --- a/packages/drizzle-pkg/lib/schema/llm.ts +++ b/packages/drizzle-pkg/lib/schema/llm.ts @@ -1,4 +1,5 @@ -import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; +import { sqliteTable, text, integer, index, uniqueIndex } from "drizzle-orm/sqlite-core"; +import { users } from "./auth"; export const LlmProviderStatuses = ["active", "disabled"] as const; export type LlmProviderStatus = (typeof LlmProviderStatuses)[number]; @@ -13,8 +14,11 @@ export const llmProviders = sqliteTable( "llm_providers", { id: integer("id").primaryKey({ autoIncrement: true }), - name: text("name", { length: 100 }).notNull().unique(), - slug: text("slug", { length: 100 }).notNull().unique(), + userId: integer("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + name: text("name", { length: 100 }).notNull(), + slug: text("slug", { length: 100 }).notNull(), baseUrl: text("base_url", { length: 500 }), parseMode: text("parse_mode", { enum: LlmParseModes }).notNull().default("openai"), apiKey: text("api_key"), @@ -27,7 +31,8 @@ export const llmProviders = sqliteTable( .notNull(), }, (table) => [ - index("idx_llm_provider_slug").on(table.slug), + uniqueIndex("idx_llm_provider_user_slug").on(table.userId, table.slug), + index("idx_llm_provider_user").on(table.userId), index("idx_llm_provider_status").on(table.status), ], ); diff --git a/packages/drizzle-pkg/migrations/0013_flaky_marvel_boy.sql b/packages/drizzle-pkg/migrations/0013_flaky_marvel_boy.sql new file mode 100644 index 0000000..12a6437 --- /dev/null +++ b/packages/drizzle-pkg/migrations/0013_flaky_marvel_boy.sql @@ -0,0 +1,24 @@ +DROP INDEX `llm_providers_name_unique`;--> statement-breakpoint +DROP INDEX `llm_providers_slug_unique`;--> statement-breakpoint +DROP INDEX `idx_llm_provider_slug`;--> statement-breakpoint +ALTER TABLE `llm_providers` ADD `user_id` integer REFERENCES users(id);--> statement-breakpoint +UPDATE `llm_providers` SET `user_id` = (SELECT MIN(id) FROM `users`) WHERE `user_id` IS NULL;--> statement-breakpoint +CREATE TABLE `_llm_providers_tmp` ( + `id` integer PRIMARY KEY AUTOINCREMENT, + `user_id` integer NOT NULL REFERENCES users(id), + `name` text(100) NOT NULL, + `slug` text(100) NOT NULL, + `base_url` text(500), + `parse_mode` text DEFAULT 'openai' NOT NULL, + `api_key` text, + `status` text DEFAULT 'active' NOT NULL, + `description` text, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL +);--> statement-breakpoint +INSERT INTO `_llm_providers_tmp` SELECT `id`, `user_id`, `name`, `slug`, `base_url`, `parse_mode`, `api_key`, `status`, `description`, `created_at`, `updated_at` FROM `llm_providers`;--> statement-breakpoint +DROP TABLE `llm_providers`;--> statement-breakpoint +ALTER TABLE `_llm_providers_tmp` RENAME TO `llm_providers`;--> statement-breakpoint +CREATE UNIQUE INDEX `idx_llm_provider_user_slug` ON `llm_providers` (`user_id`,`slug`);--> statement-breakpoint +CREATE INDEX `idx_llm_provider_user` ON `llm_providers` (`user_id`);--> statement-breakpoint +CREATE INDEX `idx_llm_provider_status` ON `llm_providers` (`status`); diff --git a/packages/drizzle-pkg/migrations/meta/0013_snapshot.json b/packages/drizzle-pkg/migrations/meta/0013_snapshot.json new file mode 100644 index 0000000..e6448d7 --- /dev/null +++ b/packages/drizzle-pkg/migrations/meta/0013_snapshot.json @@ -0,0 +1,1674 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f4ec8869-c90b-426e-972a-2e1dc8d4e79f", + "prevId": "20c54319-abd6-41f9-956b-39d5b6d9e222", + "tables": { + "oauth_accounts": { + "name": "oauth_accounts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "app_configs": { + "name": "app_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value_type": { + "name": "value_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_configs": { + "name": "user_configs", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value_type": { + "name": "value_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "user_configs_user_id_idx": { + "name": "user_configs_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_configs_user_id_users_id_fk": { + "name": "user_configs_user_id_users_id_fk", + "tableFrom": "user_configs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_configs_user_id_key_pk": { + "columns": [ + "user_id", + "key" + ], + "name": "user_configs_user_id_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "article_cards": { + "name": "article_cards", + "columns": { + "article_id": { + "name": "article_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_article_card_article": { + "name": "idx_article_card_article", + "columns": [ + "article_id" + ], + "isUnique": false + }, + "idx_article_card_card": { + "name": "idx_article_card_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "article_cards_article_id_articles_id_fk": { + "name": "article_cards_article_id_articles_id_fk", + "tableFrom": "article_cards", + "tableTo": "articles", + "columnsFrom": [ + "article_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "article_cards_card_id_cards_id_fk": { + "name": "article_cards_card_id_cards_id_fk", + "tableFrom": "article_cards", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "article_card_pk": { + "columns": [ + "article_id", + "card_id" + ], + "name": "article_card_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "articles": { + "name": "articles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover": { + "name": "cover", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_article_status": { + "name": "idx_article_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_article_created": { + "name": "idx_article_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "card_images": { + "name": "card_images", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_card_image_card": { + "name": "idx_card_image_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "card_images_card_id_cards_id_fk": { + "name": "card_images_card_id_cards_id_fk", + "tableFrom": "card_images", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "card_tags": { + "name": "card_tags", + "columns": { + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_card_tag_card": { + "name": "idx_card_tag_card", + "columns": [ + "card_id" + ], + "isUnique": false + }, + "idx_card_tag_tag": { + "name": "idx_card_tag_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "card_tags_card_id_cards_id_fk": { + "name": "card_tags_card_id_cards_id_fk", + "tableFrom": "card_tags", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_tags_tag_id_tags_id_fk": { + "name": "card_tags_tag_id_tags_id_fk", + "tableFrom": "card_tags", + "tableTo": "tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "card_tag_pk": { + "columns": [ + "card_id", + "tag_id" + ], + "name": "card_tag_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cards": { + "name": "cards", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "aspect_ratio": { + "name": "aspect_ratio", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_card_category": { + "name": "idx_card_category", + "columns": [ + "category_id" + ], + "isUnique": false + }, + "idx_card_type": { + "name": "idx_card_type", + "columns": [ + "type" + ], + "isUnique": false + }, + "idx_card_created": { + "name": "idx_card_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "cards_category_id_categories_id_fk": { + "name": "cards_category_id_categories_id_fk", + "tableFrom": "cards", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "categories": { + "name": "categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_category_slug": { + "name": "idx_category_slug", + "columns": [ + "slug" + ], + "isUnique": true + }, + "idx_category_parent": { + "name": "idx_category_parent", + "columns": [ + "parent_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chat_messages": { + "name": "chat_messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "nickname": { + "name": "nickname", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_chat_msg_created": { + "name": "idx_chat_msg_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "favorites": { + "name": "favorites", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_favorite_user": { + "name": "idx_favorite_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_favorite_card": { + "name": "idx_favorite_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "favorites_user_id_users_id_fk": { + "name": "favorites_user_id_users_id_fk", + "tableFrom": "favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "favorites_card_id_cards_id_fk": { + "name": "favorites_card_id_cards_id_fk", + "tableFrom": "favorites", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "favorite_pk": { + "columns": [ + "user_id", + "card_id" + ], + "name": "favorite_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ideas": { + "name": "ideas", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "author": { + "name": "author", + "type": "text(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_idea_created": { + "name": "idx_idea_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text(1000)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_project_created": { + "name": "idx_project_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_slug": { + "name": "idx_tag_slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tools": { + "name": "tools", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_models": { + "name": "llm_models", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(200)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text(200)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_llm_model_provider": { + "name": "idx_llm_model_provider", + "columns": [ + "provider_id" + ], + "isUnique": false + }, + "idx_llm_model_type": { + "name": "idx_llm_model_type", + "columns": [ + "type" + ], + "isUnique": false + } + }, + "foreignKeys": { + "llm_models_provider_id_llm_providers_id_fk": { + "name": "llm_models_provider_id_llm_providers_id_fk", + "tableFrom": "llm_models", + "tableTo": "llm_providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_providers": { + "name": "llm_providers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parse_mode": { + "name": "parse_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'openai'" + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_llm_provider_user_slug": { + "name": "idx_llm_provider_user_slug", + "columns": [ + "user_id", + "slug" + ], + "isUnique": true + }, + "idx_llm_provider_user": { + "name": "idx_llm_provider_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_llm_provider_status": { + "name": "idx_llm_provider_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "llm_providers_user_id_users_id_fk": { + "name": "llm_providers_user_id_users_id_fk", + "tableFrom": "llm_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "scheduled_tasks": { + "name": "scheduled_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "function_name": { + "name": "function_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "function_payload": { + "name": "function_payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_method": { + "name": "http_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_url": { + "name": "http_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_headers": { + "name": "http_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_body": { + "name": "http_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "catch_up": { + "name": "catch_up", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "max_retries": { + "name": "max_retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "retry_delay_seconds": { + "name": "retry_delay_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 60 + }, + "timeout_seconds": { + "name": "timeout_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_execution_logs": { + "name": "task_execution_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "task_execution_logs_task_id_scheduled_tasks_id_fk": { + "name": "task_execution_logs_task_id_scheduled_tasks_id_fk", + "tableFrom": "task_execution_logs", + "tableTo": "scheduled_tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/drizzle-pkg/migrations/meta/_journal.json b/packages/drizzle-pkg/migrations/meta/_journal.json index 5778112..0d0ec79 100644 --- a/packages/drizzle-pkg/migrations/meta/_journal.json +++ b/packages/drizzle-pkg/migrations/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1782963564430, "tag": "0012_funny_santa_claus", "breakpoints": true + }, + { + "idx": 13, + "version": "6", + "when": 1782969066349, + "tag": "0013_flaky_marvel_boy", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/api/admin/llm/models/[providerId].get.ts b/server/api/llm/models/[providerId].get.ts similarity index 65% rename from server/api/admin/llm/models/[providerId].get.ts rename to server/api/llm/models/[providerId].get.ts index b9cb88a..986b649 100644 --- a/server/api/admin/llm/models/[providerId].get.ts +++ b/server/api/llm/models/[providerId].get.ts @@ -1,14 +1,14 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { listModelsByProvider } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const providerId = Number(getRouterParam(event, "providerId")); if (!providerId) { return R.throwError(400, "无效的供应商ID", null); } - const result = await listModelsByProvider(providerId); + const result = await listModelsByProvider(providerId, user.id); return R.success(result); }); diff --git a/server/api/admin/llm/models/[providerId].post.ts b/server/api/llm/models/[providerId].post.ts similarity index 74% rename from server/api/admin/llm/models/[providerId].post.ts rename to server/api/llm/models/[providerId].post.ts index b0e1633..73d859f 100644 --- a/server/api/admin/llm/models/[providerId].post.ts +++ b/server/api/llm/models/[providerId].post.ts @@ -1,8 +1,8 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { createModel } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const providerId = Number(getRouterParam(event, "providerId")); if (!providerId) { @@ -16,7 +16,7 @@ export default defineWrappedResponseHandler(async (event) => { return R.throwError(422, "模型名称和模型ID不能为空", null); } - const result = await createModel({ + const result = await createModel(user.id, { providerId, name, modelId, @@ -25,5 +25,8 @@ export default defineWrappedResponseHandler(async (event) => { description, maxTokens, }); + if (!result) { + return R.throwError(403, "无权操作该供应商", null); + } return R.success(result); }); diff --git a/server/api/admin/llm/models/detail/[id].delete.ts b/server/api/llm/models/detail/[id].delete.ts similarity index 67% rename from server/api/admin/llm/models/detail/[id].delete.ts rename to server/api/llm/models/detail/[id].delete.ts index 6789d9a..8632e05 100644 --- a/server/api/admin/llm/models/detail/[id].delete.ts +++ b/server/api/llm/models/detail/[id].delete.ts @@ -1,14 +1,14 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { deleteModel } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const id = Number(getRouterParam(event, "id")); if (!id) { return R.throwError(400, "无效的模型ID", null); } - await deleteModel(id); + await deleteModel(id, user.id); return R.success(null); }); diff --git a/server/api/admin/llm/models/detail/[id].put.ts b/server/api/llm/models/detail/[id].put.ts similarity index 78% rename from server/api/admin/llm/models/detail/[id].put.ts rename to server/api/llm/models/detail/[id].put.ts index 097fe08..610f7c1 100644 --- a/server/api/admin/llm/models/detail/[id].put.ts +++ b/server/api/llm/models/detail/[id].put.ts @@ -1,8 +1,8 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { updateModel } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const id = Number(getRouterParam(event, "id")); if (!id) { @@ -12,7 +12,7 @@ export default defineWrappedResponseHandler(async (event) => { const body = await readBody(event); const { name, modelId, type, enabled, description, maxTokens } = body; - await updateModel(id, { + await updateModel(id, user.id, { name, modelId, type, diff --git a/server/api/admin/llm/providers/[id].delete.ts b/server/api/llm/providers/[id].delete.ts similarity index 67% rename from server/api/admin/llm/providers/[id].delete.ts rename to server/api/llm/providers/[id].delete.ts index f75adb3..dcec566 100644 --- a/server/api/admin/llm/providers/[id].delete.ts +++ b/server/api/llm/providers/[id].delete.ts @@ -1,14 +1,14 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { deleteProvider } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const id = Number(getRouterParam(event, "id")); if (!id) { return R.throwError(400, "无效的供应商ID", null); } - await deleteProvider(id); + await deleteProvider(id, user.id); return R.success(null); }); diff --git a/server/api/admin/llm/providers/[id].get.ts b/server/api/llm/providers/[id].get.ts similarity index 70% rename from server/api/admin/llm/providers/[id].get.ts rename to server/api/llm/providers/[id].get.ts index a8d03ba..040820a 100644 --- a/server/api/admin/llm/providers/[id].get.ts +++ b/server/api/llm/providers/[id].get.ts @@ -1,15 +1,15 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { getProviderWithModels } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const id = Number(getRouterParam(event, "id")); if (!id) { return R.throwError(400, "无效的供应商ID", null); } - const result = await getProviderWithModels(id); + const result = await getProviderWithModels(id, user.id); if (!result) { return R.throwError(404, "供应商不存在", null); } diff --git a/server/api/admin/llm/providers/[id].models.get.ts b/server/api/llm/providers/[id].models.get.ts similarity index 88% rename from server/api/admin/llm/providers/[id].models.get.ts rename to server/api/llm/providers/[id].models.get.ts index a6211f8..b1e1406 100644 --- a/server/api/admin/llm/providers/[id].models.get.ts +++ b/server/api/llm/providers/[id].models.get.ts @@ -1,15 +1,15 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { getProviderById } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const id = Number(getRouterParam(event, "id")); if (!id) { return R.throwError(400, "无效的供应商 ID", null); } - const provider = await getProviderById(id); + const provider = await getProviderById(id, user.id); if (!provider) { return R.throwError(404, "供应商不存在", null); } diff --git a/server/api/admin/llm/providers/[id].put.ts b/server/api/llm/providers/[id].put.ts similarity index 78% rename from server/api/admin/llm/providers/[id].put.ts rename to server/api/llm/providers/[id].put.ts index ec94f99..35e6385 100644 --- a/server/api/admin/llm/providers/[id].put.ts +++ b/server/api/llm/providers/[id].put.ts @@ -1,8 +1,8 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { updateProvider } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const id = Number(getRouterParam(event, "id")); if (!id) { @@ -12,7 +12,7 @@ export default defineWrappedResponseHandler(async (event) => { const body = await readBody(event); const { name, slug, baseUrl, parseMode, apiKey, status, description } = body; - await updateProvider(id, { + await updateProvider(id, user.id, { name, slug, baseUrl, diff --git a/server/api/admin/llm/providers/index.get.ts b/server/api/llm/providers/index.get.ts similarity index 67% rename from server/api/admin/llm/providers/index.get.ts rename to server/api/llm/providers/index.get.ts index 93b2044..69e759b 100644 --- a/server/api/admin/llm/providers/index.get.ts +++ b/server/api/llm/providers/index.get.ts @@ -1,8 +1,8 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { listProviders } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const query = getQuery(event); const page = Number(query.page) || 1; @@ -10,6 +10,6 @@ export default defineWrappedResponseHandler(async (event) => { const search = query.search as string | undefined; const status = query.status as string | undefined; - const result = await listProviders(page, pageSize, search, status as any); + const result = await listProviders(user.id, page, pageSize, search, status as any); return R.success(result); }); diff --git a/server/api/admin/llm/providers/index.post.ts b/server/api/llm/providers/index.post.ts similarity index 78% rename from server/api/admin/llm/providers/index.post.ts rename to server/api/llm/providers/index.post.ts index 0938a69..88f729f 100644 --- a/server/api/admin/llm/providers/index.post.ts +++ b/server/api/llm/providers/index.post.ts @@ -1,8 +1,8 @@ -import { requireAdmin } from "#server/utils/admin-guard"; +import { requireUser } from "#server/utils/context"; import { createProvider } from "#server/service/llm"; export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); + const user = await requireUser(event); const body = await readBody(event); const { name, slug, baseUrl, parseMode, apiKey, status, description } = body; @@ -11,7 +11,7 @@ export default defineWrappedResponseHandler(async (event) => { return R.throwError(422, "供应商名称和标识不能为空", null); } - const result = await createProvider({ + const result = await createProvider(user.id, { name, slug, baseUrl, diff --git a/server/service/llm/index.ts b/server/service/llm/index.ts index dcbaea1..b972d26 100644 --- a/server/service/llm/index.ts +++ b/server/service/llm/index.ts @@ -5,6 +5,7 @@ import type { LlmParseMode, LlmProviderStatus, LlmModelType } from "drizzle-pkg/ export interface LlmProviderRow { id: number; + userId: number; name: string; slug: string; baseUrl: string | null; @@ -35,12 +36,13 @@ export interface LlmProviderWithModels extends Omit { } export async function listProviders( + userId: number, page = 1, pageSize = 20, search?: string, status?: LlmProviderStatus, ) { - const conditions = []; + const conditions = [eq(llmProviders.userId, userId)]; if (search) { conditions.push(like(llmProviders.name, `%${search}%`)); } @@ -48,7 +50,7 @@ export async function listProviders( conditions.push(eq(llmProviders.status, status)); } - const where = conditions.length > 0 ? and(...conditions) : undefined; + const where = and(...conditions); const [countResult] = await dbGlobal .select({ count: sql`count(*)` }) @@ -74,17 +76,17 @@ export async function listProviders( return { list: sanitized, total, page, pageSize, totalPages }; } -export async function getProviderById(id: number) { +export async function getProviderById(id: number, userId: number) { const [row] = await dbGlobal .select() .from(llmProviders) - .where(eq(llmProviders.id, id)) + .where(and(eq(llmProviders.id, id), eq(llmProviders.userId, userId))) .limit(1); return row ?? null; } -export async function getProviderWithModels(id: number): Promise { - const provider = await getProviderById(id); +export async function getProviderWithModels(id: number, userId: number): Promise { + const provider = await getProviderById(id, userId); if (!provider) return null; const models = await dbGlobal @@ -101,7 +103,7 @@ export async function getProviderWithModels(id: number): Promise = {}; if (data.name !== undefined) updates.name = data.name; if (data.modelId !== undefined) updates.modelId = data.modelId; @@ -223,6 +247,9 @@ export async function updateModel( .where(eq(llmModels.id, id)); } -export async function deleteModel(id: number) { +export async function deleteModel(id: number, userId: number) { + const model = await getModelById(id, userId); + if (!model) return; + await dbGlobal.delete(llmModels).where(eq(llmModels.id, id)); }