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.
This commit is contained in:
2025-12-31 17:55:13 +08:00
parent 8731801b52
commit c6715c2e35
9 changed files with 620 additions and 313 deletions

View File

@@ -56,6 +56,7 @@
"commander": "^14.0.2", "commander": "^14.0.2",
"drizzle-kit": "^0.31.8", "drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"drizzle-zod": "^0.8.3",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"ioredis": "^5.8.2", "ioredis": "^5.8.2",
"minio": "^8.0.6", "minio": "^8.0.6",
@@ -64,7 +65,8 @@
"send": "^1.2.1", "send": "^1.2.1",
"sequelize": "^6.37.7", "sequelize": "^6.37.7",
"ws": "npm:@kevisual/ws", "ws": "npm:@kevisual/ws",
"xml2js": "^0.6.2" "xml2js": "^0.6.2",
"zod-to-json-schema": "^3.25.1"
}, },
"devDependencies": { "devDependencies": {
"@kevisual/code-center-module": "0.0.24", "@kevisual/code-center-module": "0.0.24",

26
pnpm-lock.yaml generated
View File

@@ -42,6 +42,9 @@ importers:
drizzle-orm: drizzle-orm:
specifier: ^0.45.1 specifier: ^0.45.1
version: 0.45.1(better-sqlite3@12.5.0)(bun-types@1.3.5)(pg@8.16.3) 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: eventemitter3:
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1 version: 5.0.1
@@ -69,6 +72,9 @@ importers:
xml2js: xml2js:
specifier: ^0.6.2 specifier: ^0.6.2
version: 0.6.2 version: 0.6.2
zod-to-json-schema:
specifier: ^3.25.1
version: 3.25.1(zod@4.2.1)
devDependencies: devDependencies:
'@kevisual/code-center-module': '@kevisual/code-center-module':
specifier: 0.0.24 specifier: 0.0.24
@@ -1177,6 +1183,12 @@ packages:
sqlite3: sqlite3:
optional: true 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: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -2361,6 +2373,11 @@ packages:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'} 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: zod@3.25.67:
resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
@@ -3278,6 +3295,11 @@ snapshots:
bun-types: 1.3.5 bun-types: 1.3.5
pg: 8.16.3 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: {} eastasianwidth@0.2.0: {}
ecdsa-sig-formatter@1.0.11: ecdsa-sig-formatter@1.0.11:
@@ -4580,6 +4602,10 @@ snapshots:
compress-commons: 6.0.2 compress-commons: 6.0.2
readable-stream: 4.5.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@3.25.67: {}
zod@4.2.1: {} zod@4.2.1: {}

View File

@@ -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 { 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 enumCfRouterCodeType = pgEnum("enum_cf_router_code_type", ['route', 'middleware'])
export const testPromptTools = pgTable("TestPromptTools", { export const testPromptTools = pgTable("TestPromptTools", {
id: serial().primaryKey().notNull(), id: serial().primaryKey().notNull(),
template: text().notNull(), template: text().notNull(),
args: jsonb().notNull(), args: jsonb().notNull(),
process: jsonb().notNull(), process: jsonb().notNull(),
type: varchar({ length: 255 }).notNull(), type: varchar({ length: 255 }).notNull(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
}); });
export const aiAgent = pgTable("ai_agent", { export const aiAgent = pgTable("ai_agent", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
type: varchar({ length: 255 }).notNull(), type: varchar({ length: 255 }).notNull(),
baseUrl: varchar({ length: 255 }).notNull(), baseUrl: varchar({ length: 255 }).notNull(),
apiKey: varchar({ length: 255 }).notNull(), apiKey: varchar({ length: 255 }).notNull(),
temperature: doublePrecision(), temperature: doublePrecision(),
cache: varchar({ length: 255 }), cache: varchar({ length: 255 }),
cacheName: varchar({ length: 255 }), cacheName: varchar({ length: 255 }),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
model: varchar({ length: 255 }).notNull(), model: varchar({ length: 255 }).notNull(),
data: json().default({}), data: json().default({}),
status: varchar({ length: 255 }).default('open'), status: varchar({ length: 255 }).default('open'),
key: varchar({ length: 255 }).notNull(), key: varchar({ length: 255 }).notNull(),
description: text(), description: text(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}, (table) => [ }, (table) => [
unique("ai_agent_key_key").on(table.key), unique("ai_agent_key_key").on(table.key),
]); ]);
export const appsTrades = pgTable("apps_trades", { export const appsTrades = pgTable("apps_trades", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
outTradeNo: varchar("out_trade_no", { length: 255 }).notNull(), outTradeNo: varchar("out_trade_no", { length: 255 }).notNull(),
money: integer().notNull(), money: integer().notNull(),
subject: text().notNull(), subject: text().notNull(),
status: varchar({ length: 255 }).default('WAIT_BUYER_PAY').notNull(), status: varchar({ length: 255 }).default('WAIT_BUYER_PAY').notNull(),
type: varchar({ length: 255 }).default('alipay').notNull(), type: varchar({ length: 255 }).default('alipay').notNull(),
data: jsonb().default({"list":[]}), data: jsonb().default({ "list": [] }),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}, (table) => [ }, (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", { export const cfOrgs = pgTable("cf_orgs", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
username: varchar({ length: 255 }).notNull(), username: varchar({ length: 255 }).notNull(),
users: jsonb().default([]), users: jsonb().default([]),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
description: varchar({ length: 255 }), description: varchar({ length: 255 }),
}, (table) => [ }, (table) => [
unique("cf_orgs_username_key").on(table.username), unique("cf_orgs_username_key").on(table.username),
]); ]);
export const cfRouterCode = pgTable("cf_router_code", { export const cfRouterCode = pgTable("cf_router_code", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
path: varchar({ length: 255 }).notNull(), path: varchar({ length: 255 }).notNull(),
key: varchar({ length: 255 }).notNull(), key: varchar({ length: 255 }).notNull(),
active: boolean().default(false), active: boolean().default(false),
project: varchar({ length: 255 }).default('default'), project: varchar({ length: 255 }).default('default'),
code: text().default(''), code: text().default(''),
type: enumCfRouterCodeType().default('route'), type: enumCfRouterCodeType().default('route'),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: 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"]), middleware: varchar({ length: 255 }).array().default(["RRAY[]::character varying[])::character varying(25"]),
next: varchar({ length: 255 }).default(''), next: varchar({ length: 255 }).default(''),
exec: text().default(''), exec: text().default(''),
data: json().default({}), data: json().default({}),
validator: json().default({}), validator: json().default({}),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}); });
export const cfUser = pgTable("cf_user", { export const cfUser = pgTable("cf_user", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
username: varchar({ length: 255 }).notNull(), username: varchar({ length: 255 }).notNull(),
password: varchar({ length: 255 }), password: varchar({ length: 255 }),
salt: varchar({ length: 255 }), salt: varchar({ length: 255 }),
needChangePassword: boolean().default(false), needChangePassword: boolean().default(false),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
description: text(), description: text(),
data: jsonb().default({}), data: jsonb().default({}),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
type: varchar({ length: 255 }).default('user'), type: varchar({ length: 255 }).default('user'),
owner: uuid(), owner: uuid(),
orgId: uuid(), orgId: uuid(),
email: varchar({ length: 255 }), email: varchar({ length: 255 }),
avatar: text(), avatar: text(),
nickname: text(), nickname: text(),
}, (table) => [ }, (table) => [
unique("cf_user_username_key").on(table.username), unique("cf_user_username_key").on(table.username),
]); ]);
export const cfUserSecrets = pgTable("cf_user_secrets", { export const cfUserSecrets = pgTable("cf_user_secrets", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
description: text(), description: text(),
status: varchar({ length: 255 }).default('active'), status: varchar({ length: 255 }).default('active'),
title: text(), title: text(),
expiredTime: timestamp({ withTimezone: true, mode: 'string' }), expiredTime: timestamp({ withTimezone: true, mode: 'string' }),
token: varchar({ length: 255 }).default('').notNull(), token: varchar({ length: 255 }).default('').notNull(),
userId: uuid(), userId: uuid(),
data: jsonb().default({}), data: jsonb().default({}),
orgId: uuid(), orgId: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
}); });
export const chatHistories = pgTable("chat_histories", { export const chatHistories = pgTable("chat_histories", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
data: json(), data: json(),
chatId: uuid(), chatId: uuid(),
chatPromptId: uuid(), chatPromptId: uuid(),
root: boolean().default(false), root: boolean().default(false),
show: boolean().default(true), show: boolean().default(true),
uid: varchar({ length: 255 }), uid: varchar({ length: 255 }),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
role: varchar({ length: 255 }).default('user'), role: varchar({ length: 255 }).default('user'),
}); });
export const chatPrompts = pgTable("chat_prompts", { export const chatPrompts = pgTable("chat_prompts", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: varchar({ length: 255 }).notNull(), title: varchar({ length: 255 }).notNull(),
description: text(), description: text(),
data: json(), data: json(),
key: varchar({ length: 255 }).default('').notNull(), key: varchar({ length: 255 }).default('').notNull(),
uid: varchar({ length: 255 }), uid: varchar({ length: 255 }),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}); });
export const chatSessions = pgTable("chat_sessions", { export const chatSessions = pgTable("chat_sessions", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
data: json().default({}), data: json().default({}),
chatPromptId: uuid(), chatPromptId: uuid(),
type: varchar({ length: 255 }).default('production'), type: varchar({ length: 255 }).default('production'),
uid: varchar({ length: 255 }), uid: varchar({ length: 255 }),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
title: varchar({ length: 255 }).default(''), title: varchar({ length: 255 }).default(''),
key: varchar({ length: 255 }), key: varchar({ length: 255 }),
}); });
export const fileSync = pgTable("file_sync", { export const fileSync = pgTable("file_sync", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
name: varchar({ length: 255 }), name: varchar({ length: 255 }),
hash: varchar({ length: 255 }), hash: varchar({ length: 255 }),
stat: jsonb().default({}), stat: jsonb().default({}),
data: jsonb().default({}), data: jsonb().default({}),
checkedAt: timestamp({ withTimezone: true, mode: 'string' }), checkedAt: timestamp({ withTimezone: true, mode: 'string' }),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
}, (table) => [ }, (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", { export const kvAiChatHistory = pgTable("kv_ai_chat_history", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
username: varchar({ length: 255 }).default('').notNull(), username: varchar({ length: 255 }).default('').notNull(),
model: varchar({ length: 255 }).default('').notNull(), model: varchar({ length: 255 }).default('').notNull(),
group: varchar({ length: 255 }).default('').notNull(), group: varchar({ length: 255 }).default('').notNull(),
title: varchar({ length: 255 }).default('').notNull(), title: varchar({ length: 255 }).default('').notNull(),
messages: jsonb().default([]).notNull(), messages: jsonb().default([]).notNull(),
promptTokens: integer("prompt_tokens").default(0), promptTokens: integer("prompt_tokens").default(0),
totalTokens: integer("total_tokens").default(0), totalTokens: integer("total_tokens").default(0),
completionTokens: integer("completion_tokens").default(0), completionTokens: integer("completion_tokens").default(0),
data: jsonb().default({}), data: jsonb().default({}),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
version: integer().default(0), version: integer().default(0),
type: varchar({ length: 255 }).default('keep').notNull(), type: varchar({ length: 255 }).default('keep').notNull(),
}); });
export const kvApp = pgTable("kv_app", { export const kvApp = pgTable("kv_app", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
data: jsonb().default({}), data: jsonb().default({}),
version: varchar({ length: 255 }).default(''), version: varchar({ length: 255 }).default(''),
key: varchar({ length: 255 }), key: varchar({ length: 255 }),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
title: varchar({ length: 255 }).default(''), title: varchar({ length: 255 }).default(''),
description: varchar({ length: 255 }).default(''), description: varchar({ length: 255 }).default(''),
user: varchar({ length: 255 }), user: varchar({ length: 255 }),
status: varchar({ length: 255 }).default('running'), status: varchar({ length: 255 }).default('running'),
pid: uuid(), pid: uuid(),
proxy: boolean().default(false), proxy: boolean().default(false),
}, (table) => [ }, (table) => [
uniqueIndex("kv_app_key_uid").using("btree", table.key.asc().nullsLast().op("text_ops"), table.uid.asc().nullsLast().op("text_ops")), uniqueIndex("kv_app_key_uid").using("btree", table.key.asc().nullsLast(), table.uid.asc().nullsLast()),
unique("key_uid_unique").on(table.key, table.uid), unique("key_uid_unique").on(table.key, table.uid),
]); ]);
export const kvAppDomain = pgTable("kv_app_domain", { export const kvAppDomain = pgTable("kv_app_domain", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
domain: varchar({ length: 255 }).notNull(), domain: varchar({ length: 255 }).notNull(),
appId: varchar({ length: 255 }), appId: varchar({ length: 255 }),
uid: varchar({ length: 255 }), uid: varchar({ length: 255 }),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
data: jsonb(), data: jsonb(),
status: varchar({ length: 255 }).default('running').notNull(), status: varchar({ length: 255 }).default('running').notNull(),
}, (table) => [ }, (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", { export const kvAppList = pgTable("kv_app_list", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
data: json().default({}), data: json().default({}),
version: varchar({ length: 255 }).default(''), version: varchar({ length: 255 }).default(''),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
key: varchar({ length: 255 }), key: varchar({ length: 255 }),
status: varchar({ length: 255 }).default('running'), status: varchar({ length: 255 }).default('running'),
}); });
export const kvConfig = pgTable("kv_config", { export const kvConfig = pgTable("kv_config", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: text().default(''), title: text().default(''),
key: text().default(''), key: text().default(''),
description: text().default(''), description: text().default(''),
tags: jsonb().default([]), tags: jsonb().default([]),
data: jsonb().default({}), data: jsonb().default({}),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
hash: text().default(''), hash: text().default(''),
}); });
export const kvContainer = pgTable("kv_container", { export const kvContainer = pgTable("kv_container", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: text().default(''), title: text().default(''),
description: text().default(''), description: text().default(''),
type: varchar({ length: 255 }).default('render-js'), type: varchar({ length: 255 }).default('render-js'),
code: text().default(''), code: text().default(''),
data: json().default({}), data: json().default({}),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
uid: uuid(), uid: uuid(),
publish: json().default({}), publish: json().default({}),
tags: json().default([]), tags: json().default([]),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
hash: text().default(''), hash: text().default(''),
}); });
export const kvGithub = pgTable("kv_github", { export const kvGithub = pgTable("kv_github", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: varchar({ length: 255 }).default(''), title: varchar({ length: 255 }).default(''),
githubToken: varchar({ length: 255 }).default(''), githubToken: varchar({ length: 255 }).default(''),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}); });
export const kvPackages = pgTable("kv_packages", { export const kvPackages = pgTable("kv_packages", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: text().default(''), title: text().default(''),
description: text().default(''), description: text().default(''),
tags: jsonb().default([]), tags: jsonb().default([]),
data: jsonb().default({}), data: jsonb().default({}),
publish: jsonb().default({}), publish: jsonb().default({}),
expand: jsonb().default({}), expand: jsonb().default({}),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}); });
export const kvPage = pgTable("kv_page", { export const kvPage = pgTable("kv_page", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: varchar({ length: 255 }).default(''), title: varchar({ length: 255 }).default(''),
description: text().default(''), description: text().default(''),
type: varchar({ length: 255 }).default(''), type: varchar({ length: 255 }).default(''),
data: json().default({}), data: json().default({}),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
publish: json().default({}), publish: json().default({}),
}); });
export const kvResource = pgTable("kv_resource", { export const kvResource = pgTable("kv_resource", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
name: varchar({ length: 255 }).default(''), name: varchar({ length: 255 }).default(''),
description: text().default(''), description: text().default(''),
source: varchar({ length: 255 }).default(''), source: varchar({ length: 255 }).default(''),
sourceId: varchar({ length: 255 }).default(''), sourceId: varchar({ length: 255 }).default(''),
version: varchar({ length: 255 }).default('0.0.0'), version: varchar({ length: 255 }).default('0.0.0'),
data: json().default({}), data: json().default({}),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}); });
export const kvVip = pgTable("kv_vip", { export const kvVip = pgTable("kv_vip", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
userId: uuid().notNull(), userId: uuid().notNull(),
level: varchar({ length: 255 }).default('free'), level: varchar({ length: 255 }).default('free'),
category: varchar({ length: 255 }).notNull(), category: varchar({ length: 255 }).notNull(),
startDate: timestamp({ withTimezone: true, mode: 'string' }), startDate: timestamp({ withTimezone: true, mode: 'string' }),
endDate: timestamp({ withTimezone: true, mode: 'string' }), endDate: timestamp({ withTimezone: true, mode: 'string' }),
data: jsonb().default({}), data: jsonb().default({}),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
title: text().default('').notNull(), title: text().default('').notNull(),
description: text().default('').notNull(), description: text().default('').notNull(),
}); });
export const microAppsUpload = pgTable("micro_apps_upload", { export const microAppsUpload = pgTable("micro_apps_upload", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: varchar({ length: 255 }).default(''), title: varchar({ length: 255 }).default(''),
description: varchar({ length: 255 }).default(''), description: varchar({ length: 255 }).default(''),
tags: jsonb().default([]), tags: jsonb().default([]),
type: varchar({ length: 255 }).default(''), type: varchar({ length: 255 }).default(''),
source: varchar({ length: 255 }).default(''), source: varchar({ length: 255 }).default(''),
data: jsonb().default({}), data: jsonb().default({}),
share: boolean().default(false), share: boolean().default(false),
uname: varchar({ length: 255 }).default(''), uname: varchar({ length: 255 }).default(''),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
}); });
export const microMark = pgTable("micro_mark", { export const microMark = pgTable("micro_mark", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: text().default(''), title: text().default(''),
description: text().default(''), description: text().default(''),
tags: jsonb().default([]), tags: jsonb().default([]),
data: jsonb().default({}), data: jsonb().default({}),
uname: varchar({ length: 255 }).default(''), uname: varchar({ length: 255 }).default(''),
uid: uuid(), uid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
cover: text().default(''), cover: text().default(''),
thumbnail: text().default(''), thumbnail: text().default(''),
link: text().default(''), link: text().default(''),
summary: text().default(''), summary: text().default(''),
markType: text().default('md'), markType: text().default('md'),
config: jsonb().default({}), config: jsonb().default({}),
puid: uuid(), puid: uuid(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
version: integer().default(1), version: integer().default(1),
fileList: jsonb().default([]), fileList: jsonb().default([]),
key: text().default(''), key: text().default(''),
}); });
export const workShareMark = pgTable("work_share_mark", { export const workShareMark = pgTable("work_share_mark", {
id: uuid().primaryKey().notNull(), id: uuid().primaryKey().notNull(),
title: text().default(''), title: text().default(''),
key: text().default(''), key: text().default(''),
markType: text().default('md'), markType: text().default('md'),
description: text().default(''), description: text().default(''),
cover: text().default(''), cover: text().default(''),
link: text().default(''), link: text().default(''),
tags: jsonb().default([]), tags: jsonb().default([]),
summary: text().default(''), summary: text().default(''),
config: jsonb().default({}), config: jsonb().default({}),
data: jsonb().default({}), data: jsonb().default({}),
fileList: jsonb().default([]), fileList: jsonb().default([]),
uname: varchar({ length: 255 }).default(''), uname: varchar({ length: 255 }).default(''),
version: integer().default(1), version: integer().default(1),
markedAt: timestamp({ withTimezone: true, mode: 'string' }), markedAt: timestamp({ withTimezone: true, mode: 'string' }),
uid: uuid(), uid: uuid(),
puid: uuid(), puid: uuid(),
createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(), updatedAt: timestamp({ withTimezone: true, mode: 'string' }).notNull(),
deletedAt: timestamp({ withTimezone: true, mode: 'string' }), deletedAt: timestamp({ withTimezone: true, mode: 'string' }),
}); });
export const prompts = pgTable('cf_prompts', { export const prompts = pgTable('cf_prompts', {
id: uuid('id').primaryKey().defaultRandom(), id: uuid('id').primaryKey().defaultRandom(),
uid: uuid('uid'), uid: uuid('uid'),
parents: jsonb('parents').notNull().default([]), parents: text('parents').array().notNull().default([]),
data: jsonb('data').notNull().default({}), data: jsonb('data').notNull().default({}),
title: text('title').default(''), title: text('title').default(''),
@@ -402,4 +402,77 @@ export const prompts = pgTable('cf_prompts', {
createdAt: timestamp('createdAt').notNull().defaultNow(), createdAt: timestamp('createdAt').notNull().defaultNow(),
updatedAt: timestamp('updatedAt').notNull().defaultNow(), updatedAt: timestamp('updatedAt').notNull().defaultNow(),
deletedAt: timestamp('deletedAt'), deletedAt: timestamp('deletedAt'),
}); }, (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<RouterViewItem>
}>(),
views: jsonb().default([]).$type<Array<RouterViewQuery>>(),
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),
]);

View File

@@ -13,4 +13,6 @@ import './file-listener/index.ts';
import './ai/index.ts'; import './ai/index.ts';
import './prompts/index.ts' import './prompts/index.ts'
import './views/index.ts';

View File

@@ -80,6 +80,9 @@ app.route({
if (existing.length === 0) { if (existing.length === 0) {
ctx.throw(404, '没有找到对应的提示词'); ctx.throw(404, '没有找到对应的提示词');
} }
if (existing[0].uid !== tokenUser.id) {
ctx.throw(403, '没有权限更新该提示词');
}
prompt = await db.update(schema.prompts).set({ prompt = await db.update(schema.prompts).set({
...rest, ...rest,
}).where(eq(schema.prompts.id, id)).returning(); }).where(eq(schema.prompts.id, id)).returning();
@@ -108,4 +111,25 @@ app.route({
} }
await db.delete(schema.prompts).where(eq(schema.prompts.id, id)); await db.delete(schema.prompts).where(eq(schema.prompts.id, id));
ctx.body = { success: true }; 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); }).addTo(app);

View File

@@ -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);

View File

@@ -0,0 +1,2 @@
import './list.ts'
import './current.ts'

141
src/routes/views/list.ts Normal file
View File

@@ -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);

View File

@@ -1,6 +1,6 @@
import { useConfig, useContextKey } from '@kevisual/context'; import { useConfig } from '@kevisual/use-config';
import { Query } from '@kevisual/query'; import { Query } from '@kevisual/query';
import util from 'node:util'; import util from 'node:util';
const config = useConfig(); const config = useConfig();
@@ -11,13 +11,13 @@ const token = 'st_r3u38c0jbhoc412ovzeeuaucygt6w5qg';
export const query = new Query({ export const query = new Query({
url: 'http://localhost:4005/api/router', url: 'http://localhost:4005/api/router',
}); });
// const loginRes = await query.post({ const loginRes = await query.post({
// path: 'user', path: 'user',
// key: 'login', key: 'login',
// username: 'root', username: 'root',
// password: config.KEVISUAL_PASSWORD ||'', password: config.KEVISUAL_PASSWORD || '',
// }); });
// console.log('login:', showMore(loginRes)); console.log('login:', showMore(loginRes));
query.beforeRequest = async (options) => { query.beforeRequest = async (options) => {
options.headers = { options.headers = {
...options.headers, ...options.headers,