From c6715c2e35cf4b45ca48feb082f0b3d15a900256 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Wed, 31 Dec 2025 17:55:13 +0800 Subject: [PATCH] feat: enhance router views functionality and permissions - Added new router views schema and types for better structure and type safety. - Implemented CRUD operations for router views including listing, updating, retrieving, and deleting views. - Introduced permission checks to ensure users can only access and modify their own views. - Updated prompts route to include additional permission checks for updating and retrieving prompts. - Refactored common query tests to align with new configurations. - Organized route imports for better maintainability. --- package.json | 4 +- pnpm-lock.yaml | 26 ++ src/db/drizzle/schema.ts | 679 ++++++++++++++++++++---------------- src/routes/index.ts | 4 +- src/routes/prompts/list.ts | 24 ++ src/routes/views/current.ts | 37 ++ src/routes/views/index.ts | 2 + src/routes/views/list.ts | 141 ++++++++ src/test/common-query.ts | 16 +- 9 files changed, 620 insertions(+), 313 deletions(-) create mode 100644 src/routes/views/current.ts create mode 100644 src/routes/views/index.ts create mode 100644 src/routes/views/list.ts diff --git a/package.json b/package.json index 7185292..38f847f 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "commander": "^14.0.2", "drizzle-kit": "^0.31.8", "drizzle-orm": "^0.45.1", + "drizzle-zod": "^0.8.3", "eventemitter3": "^5.0.1", "ioredis": "^5.8.2", "minio": "^8.0.6", @@ -64,7 +65,8 @@ "send": "^1.2.1", "sequelize": "^6.37.7", "ws": "npm:@kevisual/ws", - "xml2js": "^0.6.2" + "xml2js": "^0.6.2", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@kevisual/code-center-module": "0.0.24", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6968980..9dc546d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: drizzle-orm: specifier: ^0.45.1 version: 0.45.1(better-sqlite3@12.5.0)(bun-types@1.3.5)(pg@8.16.3) + drizzle-zod: + specifier: ^0.8.3 + version: 0.8.3(drizzle-orm@0.45.1(better-sqlite3@12.5.0)(bun-types@1.3.5)(pg@8.16.3))(zod@4.2.1) eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -69,6 +72,9 @@ importers: xml2js: specifier: ^0.6.2 version: 0.6.2 + zod-to-json-schema: + specifier: ^3.25.1 + version: 3.25.1(zod@4.2.1) devDependencies: '@kevisual/code-center-module': specifier: 0.0.24 @@ -1177,6 +1183,12 @@ packages: sqlite3: optional: true + drizzle-zod@0.8.3: + resolution: {integrity: sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==} + peerDependencies: + drizzle-orm: '>=0.36.0' + zod: ^3.25.0 || ^4.0.0 + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2361,6 +2373,11 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + zod@3.25.67: resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} @@ -3278,6 +3295,11 @@ snapshots: bun-types: 1.3.5 pg: 8.16.3 + drizzle-zod@0.8.3(drizzle-orm@0.45.1(better-sqlite3@12.5.0)(bun-types@1.3.5)(pg@8.16.3))(zod@4.2.1): + dependencies: + drizzle-orm: 0.45.1(better-sqlite3@12.5.0)(bun-types@1.3.5)(pg@8.16.3) + zod: 4.2.1 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -4580,6 +4602,10 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.5.2 + zod-to-json-schema@3.25.1(zod@4.2.1): + dependencies: + zod: 4.2.1 + zod@3.25.67: {} zod@4.2.1: {} diff --git a/src/db/drizzle/schema.ts b/src/db/drizzle/schema.ts index 1912d32..d3f2746 100644 --- a/src/db/drizzle/schema.ts +++ b/src/db/drizzle/schema.ts @@ -1,396 +1,396 @@ import { pgTable, serial, text, jsonb, varchar, timestamp, unique, uuid, doublePrecision, json, integer, boolean, index, uniqueIndex, pgEnum } from "drizzle-orm/pg-core" -import { sql } from "drizzle-orm" +import { sql, sum } from "drizzle-orm" export const enumCfRouterCodeType = pgEnum("enum_cf_router_code_type", ['route', 'middleware']) export const testPromptTools = pgTable("TestPromptTools", { - id: serial().primaryKey().notNull(), - template: text().notNull(), - args: jsonb().notNull(), - process: jsonb().notNull(), - type: varchar({ length: 255 }).notNull(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + id: serial().primaryKey().notNull(), + template: text().notNull(), + args: jsonb().notNull(), + process: jsonb().notNull(), + type: varchar({ length: 255 }).notNull(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), }); export const aiAgent = pgTable("ai_agent", { - id: uuid().primaryKey().notNull(), - type: varchar({ length: 255 }).notNull(), - baseUrl: varchar({ length: 255 }).notNull(), - apiKey: varchar({ length: 255 }).notNull(), - temperature: doublePrecision(), - cache: varchar({ length: 255 }), - cacheName: varchar({ length: 255 }), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - model: varchar({ length: 255 }).notNull(), - data: json().default({}), - status: varchar({ length: 255 }).default('open'), - key: varchar({ length: 255 }).notNull(), - description: text(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + type: varchar({ length: 255 }).notNull(), + baseUrl: varchar({ length: 255 }).notNull(), + apiKey: varchar({ length: 255 }).notNull(), + temperature: doublePrecision(), + cache: varchar({ length: 255 }), + cacheName: varchar({ length: 255 }), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + model: varchar({ length: 255 }).notNull(), + data: json().default({}), + status: varchar({ length: 255 }).default('open'), + key: varchar({ length: 255 }).notNull(), + description: text(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }, (table) => [ - unique("ai_agent_key_key").on(table.key), + unique("ai_agent_key_key").on(table.key), ]); export const appsTrades = pgTable("apps_trades", { - id: uuid().primaryKey().notNull(), - outTradeNo: varchar("out_trade_no", { length: 255 }).notNull(), - money: integer().notNull(), - subject: text().notNull(), - status: varchar({ length: 255 }).default('WAIT_BUYER_PAY').notNull(), - type: varchar({ length: 255 }).default('alipay').notNull(), - data: jsonb().default({"list":[]}), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + outTradeNo: varchar("out_trade_no", { length: 255 }).notNull(), + money: integer().notNull(), + subject: text().notNull(), + status: varchar({ length: 255 }).default('WAIT_BUYER_PAY').notNull(), + type: varchar({ length: 255 }).default('alipay').notNull(), + data: jsonb().default({ "list": [] }), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }, (table) => [ - unique("apps_trades_out_trade_no_key").on(table.outTradeNo), + unique("apps_trades_out_trade_no_key").on(table.outTradeNo), ]); export const cfOrgs = pgTable("cf_orgs", { - id: uuid().primaryKey().notNull(), - username: varchar({ length: 255 }).notNull(), - users: jsonb().default([]), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - description: varchar({ length: 255 }), + id: uuid().primaryKey().notNull(), + username: varchar({ length: 255 }).notNull(), + users: jsonb().default([]), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + description: varchar({ length: 255 }), }, (table) => [ - unique("cf_orgs_username_key").on(table.username), + unique("cf_orgs_username_key").on(table.username), ]); export const cfRouterCode = pgTable("cf_router_code", { - id: uuid().primaryKey().notNull(), - path: varchar({ length: 255 }).notNull(), - key: varchar({ length: 255 }).notNull(), - active: boolean().default(false), - project: varchar({ length: 255 }).default('default'), - code: text().default(''), - type: enumCfRouterCodeType().default('route'), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - middleware: varchar({ length: 255 }).array().default(["RRAY[]::character varying[])::character varying(25"]), - next: varchar({ length: 255 }).default(''), - exec: text().default(''), - data: json().default({}), - validator: json().default({}), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + path: varchar({ length: 255 }).notNull(), + key: varchar({ length: 255 }).notNull(), + active: boolean().default(false), + project: varchar({ length: 255 }).default('default'), + code: text().default(''), + type: enumCfRouterCodeType().default('route'), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + middleware: varchar({ length: 255 }).array().default(["RRAY[]::character varying[])::character varying(25"]), + next: varchar({ length: 255 }).default(''), + exec: text().default(''), + data: json().default({}), + validator: json().default({}), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }); export const cfUser = pgTable("cf_user", { - id: uuid().primaryKey().notNull(), - username: varchar({ length: 255 }).notNull(), - password: varchar({ length: 255 }), - salt: varchar({ length: 255 }), - needChangePassword: boolean().default(false), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - description: text(), - data: jsonb().default({}), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - type: varchar({ length: 255 }).default('user'), - owner: uuid(), - orgId: uuid(), - email: varchar({ length: 255 }), - avatar: text(), - nickname: text(), + id: uuid().primaryKey().notNull(), + username: varchar({ length: 255 }).notNull(), + password: varchar({ length: 255 }), + salt: varchar({ length: 255 }), + needChangePassword: boolean().default(false), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + description: text(), + data: jsonb().default({}), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + type: varchar({ length: 255 }).default('user'), + owner: uuid(), + orgId: uuid(), + email: varchar({ length: 255 }), + avatar: text(), + nickname: text(), }, (table) => [ - unique("cf_user_username_key").on(table.username), + unique("cf_user_username_key").on(table.username), ]); export const cfUserSecrets = pgTable("cf_user_secrets", { - id: uuid().primaryKey().notNull(), - description: text(), - status: varchar({ length: 255 }).default('active'), - title: text(), - expiredTime: timestamp({ withTimezone: true, mode: 'string' }), - token: varchar({ length: 255 }).default('').notNull(), - userId: uuid(), - data: jsonb().default({}), - orgId: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + id: uuid().primaryKey().notNull(), + description: text(), + status: varchar({ length: 255 }).default('active'), + title: text(), + expiredTime: timestamp({ withTimezone: true, mode: 'string' }), + token: varchar({ length: 255 }).default('').notNull(), + userId: uuid(), + data: jsonb().default({}), + orgId: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), }); export const chatHistories = pgTable("chat_histories", { - id: uuid().primaryKey().notNull(), - data: json(), - chatId: uuid(), - chatPromptId: uuid(), - root: boolean().default(false), - show: boolean().default(true), - uid: varchar({ length: 255 }), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - role: varchar({ length: 255 }).default('user'), + id: uuid().primaryKey().notNull(), + data: json(), + chatId: uuid(), + chatPromptId: uuid(), + root: boolean().default(false), + show: boolean().default(true), + uid: varchar({ length: 255 }), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + role: varchar({ length: 255 }).default('user'), }); export const chatPrompts = pgTable("chat_prompts", { - id: uuid().primaryKey().notNull(), - title: varchar({ length: 255 }).notNull(), - description: text(), - data: json(), - key: varchar({ length: 255 }).default('').notNull(), - uid: varchar({ length: 255 }), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + title: varchar({ length: 255 }).notNull(), + description: text(), + data: json(), + key: varchar({ length: 255 }).default('').notNull(), + uid: varchar({ length: 255 }), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }); export const chatSessions = pgTable("chat_sessions", { - id: uuid().primaryKey().notNull(), - data: json().default({}), - chatPromptId: uuid(), - type: varchar({ length: 255 }).default('production'), - uid: varchar({ length: 255 }), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - title: varchar({ length: 255 }).default(''), - key: varchar({ length: 255 }), + id: uuid().primaryKey().notNull(), + data: json().default({}), + chatPromptId: uuid(), + type: varchar({ length: 255 }).default('production'), + uid: varchar({ length: 255 }), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + title: varchar({ length: 255 }).default(''), + key: varchar({ length: 255 }), }); export const fileSync = pgTable("file_sync", { - id: uuid().primaryKey().notNull(), - name: varchar({ length: 255 }), - hash: varchar({ length: 255 }), - stat: jsonb().default({}), - data: jsonb().default({}), - checkedAt: timestamp({ withTimezone: true, mode: 'string' }), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + id: uuid().primaryKey().notNull(), + name: varchar({ length: 255 }), + hash: varchar({ length: 255 }), + stat: jsonb().default({}), + data: jsonb().default({}), + checkedAt: timestamp({ withTimezone: true, mode: 'string' }), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), }, (table) => [ - index("file_sync_name_idx").using("btree", table.name.asc().nullsLast().op("text_ops")), + index("file_sync_name_idx").using("btree", table.name.asc().nullsLast()), ]); export const kvAiChatHistory = pgTable("kv_ai_chat_history", { - id: uuid().primaryKey().notNull(), - username: varchar({ length: 255 }).default('').notNull(), - model: varchar({ length: 255 }).default('').notNull(), - group: varchar({ length: 255 }).default('').notNull(), - title: varchar({ length: 255 }).default('').notNull(), - messages: jsonb().default([]).notNull(), - promptTokens: integer("prompt_tokens").default(0), - totalTokens: integer("total_tokens").default(0), - completionTokens: integer("completion_tokens").default(0), - data: jsonb().default({}), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - version: integer().default(0), - type: varchar({ length: 255 }).default('keep').notNull(), + id: uuid().primaryKey().notNull(), + username: varchar({ length: 255 }).default('').notNull(), + model: varchar({ length: 255 }).default('').notNull(), + group: varchar({ length: 255 }).default('').notNull(), + title: varchar({ length: 255 }).default('').notNull(), + messages: jsonb().default([]).notNull(), + promptTokens: integer("prompt_tokens").default(0), + totalTokens: integer("total_tokens").default(0), + completionTokens: integer("completion_tokens").default(0), + data: jsonb().default({}), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + version: integer().default(0), + type: varchar({ length: 255 }).default('keep').notNull(), }); export const kvApp = pgTable("kv_app", { - id: uuid().primaryKey().notNull(), - data: jsonb().default({}), - version: varchar({ length: 255 }).default(''), - key: varchar({ length: 255 }), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - title: varchar({ length: 255 }).default(''), - description: varchar({ length: 255 }).default(''), - user: varchar({ length: 255 }), - status: varchar({ length: 255 }).default('running'), - pid: uuid(), - proxy: boolean().default(false), + id: uuid().primaryKey().notNull(), + data: jsonb().default({}), + version: varchar({ length: 255 }).default(''), + key: varchar({ length: 255 }), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + title: varchar({ length: 255 }).default(''), + description: varchar({ length: 255 }).default(''), + user: varchar({ length: 255 }), + status: varchar({ length: 255 }).default('running'), + pid: uuid(), + proxy: boolean().default(false), }, (table) => [ - uniqueIndex("kv_app_key_uid").using("btree", table.key.asc().nullsLast().op("text_ops"), table.uid.asc().nullsLast().op("text_ops")), - unique("key_uid_unique").on(table.key, table.uid), + uniqueIndex("kv_app_key_uid").using("btree", table.key.asc().nullsLast(), table.uid.asc().nullsLast()), + unique("key_uid_unique").on(table.key, table.uid), ]); export const kvAppDomain = pgTable("kv_app_domain", { - id: uuid().primaryKey().notNull(), - domain: varchar({ length: 255 }).notNull(), - appId: varchar({ length: 255 }), - uid: varchar({ length: 255 }), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - data: jsonb(), - status: varchar({ length: 255 }).default('running').notNull(), + id: uuid().primaryKey().notNull(), + domain: varchar({ length: 255 }).notNull(), + appId: varchar({ length: 255 }), + uid: varchar({ length: 255 }), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + data: jsonb(), + status: varchar({ length: 255 }).default('running').notNull(), }, (table) => [ - unique("kv_app_domain_domain_key").on(table.domain), + unique("kv_app_domain_domain_key").on(table.domain), ]); export const kvAppList = pgTable("kv_app_list", { - id: uuid().primaryKey().notNull(), - data: json().default({}), - version: varchar({ length: 255 }).default(''), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - key: varchar({ length: 255 }), - status: varchar({ length: 255 }).default('running'), + id: uuid().primaryKey().notNull(), + data: json().default({}), + version: varchar({ length: 255 }).default(''), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + key: varchar({ length: 255 }), + status: varchar({ length: 255 }).default('running'), }); export const kvConfig = pgTable("kv_config", { - id: uuid().primaryKey().notNull(), - title: text().default(''), - key: text().default(''), - description: text().default(''), - tags: jsonb().default([]), - data: jsonb().default({}), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - hash: text().default(''), + id: uuid().primaryKey().notNull(), + title: text().default(''), + key: text().default(''), + description: text().default(''), + tags: jsonb().default([]), + data: jsonb().default({}), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + hash: text().default(''), }); export const kvContainer = pgTable("kv_container", { - id: uuid().primaryKey().notNull(), - title: text().default(''), - description: text().default(''), - type: varchar({ length: 255 }).default('render-js'), - code: text().default(''), - data: json().default({}), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - uid: uuid(), - publish: json().default({}), - tags: json().default([]), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - hash: text().default(''), + id: uuid().primaryKey().notNull(), + title: text().default(''), + description: text().default(''), + type: varchar({ length: 255 }).default('render-js'), + code: text().default(''), + data: json().default({}), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + uid: uuid(), + publish: json().default({}), + tags: json().default([]), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + hash: text().default(''), }); export const kvGithub = pgTable("kv_github", { - id: uuid().primaryKey().notNull(), - title: varchar({ length: 255 }).default(''), - githubToken: varchar({ length: 255 }).default(''), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + title: varchar({ length: 255 }).default(''), + githubToken: varchar({ length: 255 }).default(''), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }); export const kvPackages = pgTable("kv_packages", { - id: uuid().primaryKey().notNull(), - title: text().default(''), - description: text().default(''), - tags: jsonb().default([]), - data: jsonb().default({}), - publish: jsonb().default({}), - expand: jsonb().default({}), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + title: text().default(''), + description: text().default(''), + tags: jsonb().default([]), + data: jsonb().default({}), + publish: jsonb().default({}), + expand: jsonb().default({}), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }); export const kvPage = pgTable("kv_page", { - id: uuid().primaryKey().notNull(), - title: varchar({ length: 255 }).default(''), - description: text().default(''), - type: varchar({ length: 255 }).default(''), - data: json().default({}), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - publish: json().default({}), + id: uuid().primaryKey().notNull(), + title: varchar({ length: 255 }).default(''), + description: text().default(''), + type: varchar({ length: 255 }).default(''), + data: json().default({}), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + publish: json().default({}), }); export const kvResource = pgTable("kv_resource", { - id: uuid().primaryKey().notNull(), - name: varchar({ length: 255 }).default(''), - description: text().default(''), - source: varchar({ length: 255 }).default(''), - sourceId: varchar({ length: 255 }).default(''), - version: varchar({ length: 255 }).default('0.0.0'), - data: json().default({}), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + name: varchar({ length: 255 }).default(''), + description: text().default(''), + source: varchar({ length: 255 }).default(''), + sourceId: varchar({ length: 255 }).default(''), + version: varchar({ length: 255 }).default('0.0.0'), + data: json().default({}), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }); export const kvVip = pgTable("kv_vip", { - id: uuid().primaryKey().notNull(), - userId: uuid().notNull(), - level: varchar({ length: 255 }).default('free'), - category: varchar({ length: 255 }).notNull(), - startDate: timestamp({ withTimezone: true, mode: 'string' }), - endDate: timestamp({ withTimezone: true, mode: 'string' }), - data: jsonb().default({}), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - title: text().default('').notNull(), - description: text().default('').notNull(), + id: uuid().primaryKey().notNull(), + userId: uuid().notNull(), + level: varchar({ length: 255 }).default('free'), + category: varchar({ length: 255 }).notNull(), + startDate: timestamp({ withTimezone: true, mode: 'string' }), + endDate: timestamp({ withTimezone: true, mode: 'string' }), + data: jsonb().default({}), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + title: text().default('').notNull(), + description: text().default('').notNull(), }); export const microAppsUpload = pgTable("micro_apps_upload", { - id: uuid().primaryKey().notNull(), - title: varchar({ length: 255 }).default(''), - description: varchar({ length: 255 }).default(''), - tags: jsonb().default([]), - type: varchar({ length: 255 }).default(''), - source: varchar({ length: 255 }).default(''), - data: jsonb().default({}), - share: boolean().default(false), - uname: varchar({ length: 255 }).default(''), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + id: uuid().primaryKey().notNull(), + title: varchar({ length: 255 }).default(''), + description: varchar({ length: 255 }).default(''), + tags: jsonb().default([]), + type: varchar({ length: 255 }).default(''), + source: varchar({ length: 255 }).default(''), + data: jsonb().default({}), + share: boolean().default(false), + uname: varchar({ length: 255 }).default(''), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), }); export const microMark = pgTable("micro_mark", { - id: uuid().primaryKey().notNull(), - title: text().default(''), - description: text().default(''), - tags: jsonb().default([]), - data: jsonb().default({}), - uname: varchar({ length: 255 }).default(''), - uid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - cover: text().default(''), - thumbnail: text().default(''), - link: text().default(''), - summary: text().default(''), - markType: text().default('md'), - config: jsonb().default({}), - puid: uuid(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), - version: integer().default(1), - fileList: jsonb().default([]), - key: text().default(''), + id: uuid().primaryKey().notNull(), + title: text().default(''), + description: text().default(''), + tags: jsonb().default([]), + data: jsonb().default({}), + uname: varchar({ length: 255 }).default(''), + uid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + cover: text().default(''), + thumbnail: text().default(''), + link: text().default(''), + summary: text().default(''), + markType: text().default('md'), + config: jsonb().default({}), + puid: uuid(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + version: integer().default(1), + fileList: jsonb().default([]), + key: text().default(''), }); export const workShareMark = pgTable("work_share_mark", { - id: uuid().primaryKey().notNull(), - title: text().default(''), - key: text().default(''), - markType: text().default('md'), - description: text().default(''), - cover: text().default(''), - link: text().default(''), - tags: jsonb().default([]), - summary: text().default(''), - config: jsonb().default({}), - data: jsonb().default({}), - fileList: jsonb().default([]), - uname: varchar({ length: 255 }).default(''), - version: integer().default(1), - markedAt: timestamp({ withTimezone: true, mode: 'string' }), - uid: uuid(), - puid: uuid(), - createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), - deletedAt: timestamp({ withTimezone: true, mode: 'string' }), + id: uuid().primaryKey().notNull(), + title: text().default(''), + key: text().default(''), + markType: text().default('md'), + description: text().default(''), + cover: text().default(''), + link: text().default(''), + tags: jsonb().default([]), + summary: text().default(''), + config: jsonb().default({}), + data: jsonb().default({}), + fileList: jsonb().default([]), + uname: varchar({ length: 255 }).default(''), + version: integer().default(1), + markedAt: timestamp({ withTimezone: true, mode: 'string' }), + uid: uuid(), + puid: uuid(), + createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + deletedAt: timestamp({ withTimezone: true, mode: 'string' }), }); export const prompts = pgTable('cf_prompts', { id: uuid('id').primaryKey().defaultRandom(), uid: uuid('uid'), - parents: jsonb('parents').notNull().default([]), + parents: text('parents').array().notNull().default([]), data: jsonb('data').notNull().default({}), title: text('title').default(''), @@ -402,4 +402,77 @@ export const prompts = pgTable('cf_prompts', { createdAt: timestamp('createdAt').notNull().defaultNow(), updatedAt: timestamp('updatedAt').notNull().defaultNow(), deletedAt: timestamp('deletedAt'), -}); \ No newline at end of file +}, (table) => [ + index('prompts_parents_idx').using('gin', table.parents), +]); + +export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker; +export type RouterViewApi = { + id: string; + title: string; + description: string; + type: 'api', + api: { + url: string, + // 已初始化的query实例 + // query?: Query + } +} + +export type RouterViewContext = { + id: string; + title: string; + description: string; + type: 'context', + context: { + key: string, + // 从context中获取router + // router?: QueryRouterServer + } +} +export type RouterViewWorker = { + id: string; + title: string; + description: string; + type: 'worker', + worker: { + type: 'Worker' | 'SharedWorker' | 'serviceWorker', + url: string, + // 已初始化的worker实例 + // worker?: Worker | SharedWorker | ServiceWorker, + /** + * worker选项 + * default: { type: 'module' } + */ + workerOptions?: { + type: 'module' | 'classic' + } + } +} +export type RouterViewQuery = { + id: string, + query: string, + title: string +} + +export const routerViews = pgTable("router_views", { + id: uuid().primaryKey().notNull().defaultRandom(), + uid: uuid(), + + title: text('title').default(''), + summary: text('summary').default(''), + description: text('description').default(''), + tags: jsonb().default([]), + link: text('link').default(''), + data: jsonb().default({}).$type<{ + items: Array + }>(), + + views: jsonb().default([]).$type>(), + createdAt: timestamp('createdAt').notNull().defaultNow(), + updatedAt: timestamp('updatedAt').notNull().defaultNow(), +}, (table) => [ + index('router_views_uid_idx').using('btree', table.uid.asc().nullsLast()), + index('router_title_idx').using('btree', table.title.asc().nullsLast()), + index('router_views_views_idx').using('gin', table.views), +]); \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts index b61eea9..2160a15 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -13,4 +13,6 @@ import './file-listener/index.ts'; import './ai/index.ts'; -import './prompts/index.ts' \ No newline at end of file +import './prompts/index.ts' + +import './views/index.ts'; \ No newline at end of file diff --git a/src/routes/prompts/list.ts b/src/routes/prompts/list.ts index 4113cb6..475d355 100644 --- a/src/routes/prompts/list.ts +++ b/src/routes/prompts/list.ts @@ -80,6 +80,9 @@ app.route({ if (existing.length === 0) { ctx.throw(404, '没有找到对应的提示词'); } + if (existing[0].uid !== tokenUser.id) { + ctx.throw(403, '没有权限更新该提示词'); + } prompt = await db.update(schema.prompts).set({ ...rest, }).where(eq(schema.prompts.id, id)).returning(); @@ -108,4 +111,25 @@ app.route({ } await db.delete(schema.prompts).where(eq(schema.prompts.id, id)); ctx.body = { success: true }; +}).addTo(app); + +app.route({ + path: 'prompts', + key: 'get', + middleware: ['auth'], + description: '获取单个提示词, 参数: id 提示词ID', +}).define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const { id } = ctx.query.data || {}; + if (!id) { + ctx.throw(400, 'id 参数缺失'); + } + const existing = await db.select().from(schema.prompts).where(eq(schema.prompts.id, id)).limit(1); + if (existing.length === 0) { + ctx.throw(404, '没有找到对应的提示词'); + } + if (existing[0].uid !== tokenUser.id) { + ctx.throw(403, '没有权限查看该提示词'); + } + ctx.body = existing[0]; }).addTo(app); \ No newline at end of file diff --git a/src/routes/views/current.ts b/src/routes/views/current.ts new file mode 100644 index 0000000..d1c0330 --- /dev/null +++ b/src/routes/views/current.ts @@ -0,0 +1,37 @@ +import { desc, eq, count, or, like, and, sql } from 'drizzle-orm'; +import { schema, app, db } from '@/app.ts' + +app.route({ + path: 'views', + key: 'current', + middleware: ['auth'], + description: '获取包含指定viewId的视图' +}).define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const uid = tokenUser.id; + const { viewId } = ctx.query.data || {}; + + if (!viewId) { + ctx.throw(400, 'viewId 参数缺失'); + } + + const view = await db.select() + .from(schema.routerViews) + .where( + and( + eq(schema.routerViews.uid, uid), + sql`EXISTS ( + SELECT 1 + FROM jsonb_array_elements(${schema.routerViews.views}) as item + WHERE item->>'id' = ${viewId} + )` + ) + ) + .orderBy(desc(schema.routerViews.updatedAt)) + .limit(1); + + if (view.length === 0) { + ctx.throw(404, '没有找到包含该viewId的视图'); + } + ctx.body = view[0]; +}).addTo(app); \ No newline at end of file diff --git a/src/routes/views/index.ts b/src/routes/views/index.ts new file mode 100644 index 0000000..e10b0bb --- /dev/null +++ b/src/routes/views/index.ts @@ -0,0 +1,2 @@ +import './list.ts' +import './current.ts' \ No newline at end of file diff --git a/src/routes/views/list.ts b/src/routes/views/list.ts new file mode 100644 index 0000000..1444613 --- /dev/null +++ b/src/routes/views/list.ts @@ -0,0 +1,141 @@ +import { desc, eq, count, or, like, and } from 'drizzle-orm'; +import { schema, app, db } from '@/app.ts' + + +app.route({ + path: 'views', + key: 'list', + middleware: ['auth'], + description: '获取视图列表', +}).define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const uid = tokenUser.id; + const { page = 1, pageSize = 20, search, sort = 'DESC' } = ctx.query || {}; + + const offset = (page - 1) * pageSize; + const orderByField = sort === 'ASC' ? schema.routerViews.updatedAt : desc(schema.routerViews.updatedAt); + + let whereCondition = eq(schema.routerViews.uid, uid); + if (search) { + whereCondition = and( + eq(schema.routerViews.uid, uid), + or( + like(schema.routerViews.title, `%${search}%`), + like(schema.routerViews.summary, `%${search}%`) + ) + ); + } + + const [list, totalCount] = await Promise.all([ + db.select() + .from(schema.routerViews) + .where(whereCondition) + .limit(pageSize) + .offset(offset) + .orderBy(orderByField), + db.select({ count: count() }) + .from(schema.routerViews) + .where(whereCondition) + ]); + + ctx.body = { + list, + pagination: { + page, + current: page, + pageSize, + total: totalCount[0]?.count || 0, + }, + }; + return ctx; +}).addTo(app); + +const viewUpdate = `创建或更新一个视图, 参数定义: +title: 视图标题, 必填 +data: 数据, 对象, 选填 +views: 视图查询数组, 选填 +`; +app.route({ + path: 'views', + key: 'update', + middleware: ['auth'], + description: viewUpdate, +}).define(async (ctx) => { + const { id, uid, updatedAt, ...rest } = ctx.query.data || {}; + const tokenUser = ctx.state.tokenUser; + let view; + if (!id) { + view = await db.insert(schema.routerViews).values({ + title: rest.title || '', + description: rest.description || '', + summary: rest.summary || '', + tags: rest.tags || [], + link: rest.link || '', + data: rest.data || { items: [] }, + views: rest.views || [], + uid: tokenUser.id, + }).returning(); + } else { + const existing = await db.select().from(schema.routerViews).where(eq(schema.routerViews.id, id)).limit(1); + if (existing.length === 0) { + ctx.throw(404, '没有找到对应的视图'); + } + if (existing[0].uid !== tokenUser.id) { + ctx.throw(403, '没有权限更新该视图'); + } + view = await db.update(schema.routerViews).set({ + title: rest.title, + description: rest.description, + summary: rest.summary, + tags: rest.tags, + link: rest.link, + data: rest.data, + views: rest.views, + }).where(eq(schema.routerViews.id, id)).returning(); + } + ctx.body = view; +}).addTo(app); + + +app.route({ + path: 'views', + key: 'delete', + middleware: ['auth'], + description: '删除视图, 参数: id 视图ID', +}).define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const { id } = ctx.query.data || {}; + if (!id) { + ctx.throw(400, 'id 参数缺失'); + } + const existing = await db.select().from(schema.routerViews).where(eq(schema.routerViews.id, id)).limit(1); + if (existing.length === 0) { + ctx.throw(404, '没有找到对应的视图'); + } + if (existing[0].uid !== tokenUser.id) { + ctx.throw(403, '没有权限删除该视图'); + } + await db.delete(schema.routerViews).where(eq(schema.routerViews.id, id)); + ctx.body = { success: true }; +}).addTo(app); + +app.route({ + path: 'views', + key: 'get', + middleware: ['auth'], + description: '获取单个视图, 参数: id 视图ID', +}).define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const { id } = ctx.query.data || {}; + if (!id) { + ctx.throw(400, 'id 参数缺失'); + } + const existing = await db.select().from(schema.routerViews).where(eq(schema.routerViews.id, id)).limit(1); + if (existing.length === 0) { + ctx.throw(404, '没有找到对应的视图'); + } + if (existing[0].uid !== tokenUser.id) { + ctx.throw(403, '没有权限查看该视图'); + } + ctx.body = existing[0]; +}).addTo(app); \ No newline at end of file diff --git a/src/test/common-query.ts b/src/test/common-query.ts index 4ad537f..6d7ce08 100644 --- a/src/test/common-query.ts +++ b/src/test/common-query.ts @@ -1,6 +1,6 @@ -import { useConfig, useContextKey } from '@kevisual/context'; +import { useConfig } from '@kevisual/use-config'; import { Query } from '@kevisual/query'; import util from 'node:util'; const config = useConfig(); @@ -11,13 +11,13 @@ const token = 'st_r3u38c0jbhoc412ovzeeuaucygt6w5qg'; export const query = new Query({ url: 'http://localhost:4005/api/router', }); -// const loginRes = await query.post({ -// path: 'user', -// key: 'login', -// username: 'root', -// password: config.KEVISUAL_PASSWORD ||'', -// }); -// console.log('login:', showMore(loginRes)); +const loginRes = await query.post({ + path: 'user', + key: 'login', + username: 'root', + password: config.KEVISUAL_PASSWORD || '', +}); +console.log('login:', showMore(loginRes)); query.beforeRequest = async (options) => { options.headers = { ...options.headers,