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:
@@ -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
26
pnpm-lock.yaml
generated
@@ -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: {}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
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'])
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ export const fileSync = pgTable("file_sync", {
|
|||||||
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", {
|
||||||
@@ -199,7 +199,7 @@ export const kvApp = pgTable("kv_app", {
|
|||||||
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),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -390,7 +390,7 @@ export const workShareMark = pgTable("work_share_mark", {
|
|||||||
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),
|
||||||
|
]);
|
||||||
@@ -14,3 +14,5 @@ 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';
|
||||||
@@ -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();
|
||||||
@@ -109,3 +112,24 @@ 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);
|
}).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);
|
||||||
37
src/routes/views/current.ts
Normal file
37
src/routes/views/current.ts
Normal 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);
|
||||||
2
src/routes/views/index.ts
Normal file
2
src/routes/views/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import './list.ts'
|
||||||
|
import './current.ts'
|
||||||
141
src/routes/views/list.ts
Normal file
141
src/routes/views/list.ts
Normal 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);
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user