@kne/fastify-live-components-site 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,25 @@
1
1
  const fp = require('fastify-plugin');
2
2
  const { allowCors, handleOptions, normalizeHooks } = require('../utils/cors');
3
+ const { str, bool, any, list, obj, success, PERMISSION, SITE_STATUS, nodeBase, treeNode, contentShare } = require('../schema/response');
4
+
5
+ const TAG_SITE = 'Live组件站点-站点信息';
6
+ const TAG_AI = 'Live组件站点-AI';
7
+ const TAG_FILE = 'Live组件站点-文件';
8
+ const TAG_SHARE = 'Live组件站点-内容分享';
9
+
10
+ const ENTRY_HINT = '通过站点短码 shorten 鉴权(站点须存在且为开启状态,关闭返回 403,短码无效返回 401),允许任意 Origin 跨域';
11
+
12
+ const shortenParams = {
13
+ type: 'object',
14
+ required: ['shorten'],
15
+ properties: {
16
+ shorten: { type: 'string', description: '站点公开访问短码(不区分大小写)' }
17
+ }
18
+ };
19
+
20
+ const PARENT_ID = '父文件夹节点 ID,为空表示根目录;父节点须为可写文件夹';
21
+
22
+ const fileNode = obj('文件节点', nodeBase);
3
23
 
4
24
  module.exports = fp(async (fastify, options) => {
5
25
  const { services } = fastify[options.name];
@@ -28,14 +48,17 @@ module.exports = fp(async (fastify, options) => {
28
48
  {
29
49
  onRequest: allowCors,
30
50
  schema: {
51
+ tags: [TAG_SHARE],
31
52
  summary: '通过内容短码直出已保存 content(text/plain,无 code/data;不带站点 shorten)',
53
+ description: '公开访问,无需站点短码;短码无效或已过期返回 401,所属站点关闭返回 403。允许任意 Origin 跨域',
32
54
  params: {
33
55
  type: 'object',
34
56
  required: ['contentShorten'],
35
57
  properties: {
36
- contentShorten: { type: 'string' }
58
+ contentShorten: { type: 'string', description: '内容分享短码(不区分大小写)' }
37
59
  }
38
- }
60
+ },
61
+ response: success('文件已保存的内容原文,Content-Type 为 text/plain; charset=utf-8', { type: 'string' })
39
62
  }
40
63
  },
41
64
  async (request, reply) => {
@@ -52,14 +75,20 @@ module.exports = fp(async (fastify, options) => {
52
75
  {
53
76
  onRequest: withEntryAuth,
54
77
  schema: {
78
+ tags: [TAG_SITE],
55
79
  summary: '站点信息(含是否支持 AI)',
56
- params: {
80
+ description: ENTRY_HINT,
81
+ params: shortenParams,
82
+ response: success('站点公开信息', {
57
83
  type: 'object',
58
- required: ['shorten'],
84
+ additionalProperties: true,
59
85
  properties: {
60
- shorten: { type: 'string' }
86
+ name: str('站点名称'),
87
+ status: str(SITE_STATUS),
88
+ defaultPermission: str(`新建节点默认${PERMISSION}`),
89
+ aiEnabled: bool('是否可用 AI 生成:插件配置了 ai.getAIModel 且站点 options.aiEnabled 不为 false')
61
90
  }
62
- }
91
+ })
63
92
  }
64
93
  },
65
94
  async request => {
@@ -72,26 +101,38 @@ module.exports = fp(async (fastify, options) => {
72
101
  {
73
102
  onRequest: withEntryAuth,
74
103
  schema: {
104
+ tags: [TAG_AI],
75
105
  summary: '创建 AI 流式任务,返回 streamToken',
76
- params: {
106
+ description: `${ENTRY_HINT}。站点未开启 AI 返回 403;refine 模式 messages 不能为空。任务暂存于内存,2 分钟内需用 streamToken 调用 ai/stream 消费`,
107
+ params: shortenParams,
108
+ body: {
77
109
  type: 'object',
78
- required: ['shorten'],
79
110
  properties: {
80
- shorten: { type: 'string' }
111
+ mode: { type: 'string', description: '任务模式:generate 生成代码,其它值按 refine(完善需求对话)处理' },
112
+ messages: { type: 'array', description: '对话历史,元素形如 { role: user|assistant, content },其它 role 或空 content 会被忽略' },
113
+ content: { type: 'string', description: '编辑器当前整份源码' },
114
+ scope: { type: 'object', description: '编辑器当前 scope(可用变量/组件声明)' },
115
+ props: { description: '编辑器当前组件参数,形如 { name: { type, defaultValue } };非对象时忽略' },
116
+ libs: { type: 'array', description: '可用依赖库标识列表,元素会转为字符串' },
117
+ remotes: { type: 'array', description: '可用远程组件包列表,用于在 stream 阶段拉取组件文档索引' },
118
+ selection: {
119
+ type: 'object',
120
+ description: '编辑器选中片段;传入时 AI 只针对选中部分',
121
+ properties: {
122
+ code: { description: '选中片段源码' },
123
+ startLine: { description: '选中片段起始行' },
124
+ label: { description: '选中片段名称,用于提示' }
125
+ }
126
+ }
81
127
  }
82
128
  },
83
- body: {
129
+ response: success('任务已创建', {
84
130
  type: 'object',
131
+ additionalProperties: true,
85
132
  properties: {
86
- mode: { type: 'string' },
87
- messages: { type: 'array' },
88
- content: { type: 'string' },
89
- scope: { type: 'object' },
90
- libs: { type: 'array' },
91
- remotes: { type: 'array' },
92
- selection: { type: 'object' }
133
+ streamToken: str('一次性流式任务令牌,传给 ai/stream 的 token')
93
134
  }
94
- }
135
+ })
95
136
  }
96
137
  },
97
138
  async request => {
@@ -104,20 +145,19 @@ module.exports = fp(async (fastify, options) => {
104
145
  {
105
146
  onRequest: withEntryAuth,
106
147
  schema: {
148
+ tags: [TAG_AI],
107
149
  summary: 'SSE 推送 AI 流式输出',
108
- params: {
109
- type: 'object',
110
- required: ['shorten'],
111
- properties: {
112
- shorten: { type: 'string' }
113
- }
114
- },
150
+ description: `${ENTRY_HINT}。token 只能消费一次,无效或过期返回 404;成功后以 text/event-stream 推送 data: {...} 事件直到结束`,
151
+ params: shortenParams,
115
152
  query: {
116
153
  type: 'object',
117
154
  required: ['token'],
118
155
  properties: {
119
- token: { type: 'string' }
156
+ token: { type: 'string', description: 'ai/start 返回的 streamToken' }
120
157
  }
158
+ },
159
+ response: {
160
+ 200: { description: 'text/event-stream 事件流,每条为 data: JSON;出错时推送 { text: 错误信息, done: true } 后结束' }
121
161
  }
122
162
  }
123
163
  },
@@ -131,14 +171,11 @@ module.exports = fp(async (fastify, options) => {
131
171
  {
132
172
  onRequest: withEntryAuth,
133
173
  schema: {
174
+ tags: [TAG_FILE],
134
175
  summary: '获取站点文件树',
135
- params: {
136
- type: 'object',
137
- required: ['shorten'],
138
- properties: {
139
- shorten: { type: 'string' }
140
- }
141
- }
176
+ description: `${ENTRY_HINT}。同级按类型(文件夹在前)、名称排序`,
177
+ params: shortenParams,
178
+ response: success('根节点列表,文件夹带 children', { type: 'array', items: treeNode })
142
179
  }
143
180
  },
144
181
  async request => {
@@ -151,21 +188,27 @@ module.exports = fp(async (fastify, options) => {
151
188
  {
152
189
  onRequest: withEntryAuth,
153
190
  schema: {
191
+ tags: [TAG_FILE],
154
192
  summary: '读取文件内容',
155
- params: {
193
+ description: `${ENTRY_HINT}。节点不存在返回 404,非文件返回 400`,
194
+ params: shortenParams,
195
+ query: {
156
196
  type: 'object',
157
- required: ['shorten'],
197
+ required: ['id'],
158
198
  properties: {
159
- shorten: { type: 'string' }
199
+ id: { type: 'string', description: '文件节点 ID' }
160
200
  }
161
201
  },
162
- query: {
202
+ response: success('文件内容', {
163
203
  type: 'object',
164
- required: ['id'],
204
+ additionalProperties: true,
165
205
  properties: {
166
- id: { type: 'string' }
206
+ id: any('节点 ID(字符串)'),
207
+ name: str('文件名'),
208
+ permission: str(PERMISSION),
209
+ content: str('文件内容,未保存过为空字符串')
167
210
  }
168
- }
211
+ })
169
212
  }
170
213
  },
171
214
  async request => {
@@ -178,22 +221,19 @@ module.exports = fp(async (fastify, options) => {
178
221
  {
179
222
  onRequest: withEntryAuth,
180
223
  schema: {
224
+ tags: [TAG_SHARE],
181
225
  summary: '创建文件内容分享短链(可设有效期)',
182
- params: {
183
- type: 'object',
184
- required: ['shorten'],
185
- properties: {
186
- shorten: { type: 'string' }
187
- }
188
- },
226
+ description: `${ENTRY_HINT}。只能分享文件;生成的短链通过 content/:contentShorten 公开访问`,
227
+ params: shortenParams,
189
228
  body: {
190
229
  type: 'object',
191
230
  required: ['id'],
192
231
  properties: {
193
- id: { type: 'string' },
194
- expiresIn: { type: ['number', 'null'] }
232
+ id: { type: 'string', description: '文件节点 ID' },
233
+ expiresIn: { type: ['number', 'null'], description: '有效期(秒),不传、null 或不大于 0 表示永久有效' }
195
234
  }
196
- }
235
+ },
236
+ response: success('创建的分享短链', contentShare)
197
237
  }
198
238
  },
199
239
  async request => {
@@ -206,21 +246,18 @@ module.exports = fp(async (fastify, options) => {
206
246
  {
207
247
  onRequest: withEntryAuth,
208
248
  schema: {
249
+ tags: [TAG_SHARE],
209
250
  summary: '列出文件已有内容分享短链',
210
- params: {
211
- type: 'object',
212
- required: ['shorten'],
213
- properties: {
214
- shorten: { type: 'string' }
215
- }
216
- },
251
+ description: `${ENTRY_HINT}。按创建时间倒序;已过期的分享会在查询时被删除并移除短码,不出现在结果中`,
252
+ params: shortenParams,
217
253
  query: {
218
254
  type: 'object',
219
255
  required: ['id'],
220
256
  properties: {
221
- id: { type: 'string' }
257
+ id: { type: 'string', description: '文件节点 ID' }
222
258
  }
223
- }
259
+ },
260
+ response: success('未过期的分享短链列表', { type: 'array', items: contentShare })
224
261
  }
225
262
  },
226
263
  async request => {
@@ -233,21 +270,24 @@ module.exports = fp(async (fastify, options) => {
233
270
  {
234
271
  onRequest: withEntryAuth,
235
272
  schema: {
273
+ tags: [TAG_SHARE],
236
274
  summary: '删除文件内容分享短链',
237
- params: {
275
+ description: `${ENTRY_HINT}。删除分享记录并移除内容短码;分享不存在返回 404`,
276
+ params: shortenParams,
277
+ body: {
238
278
  type: 'object',
239
279
  required: ['shorten'],
240
280
  properties: {
241
- shorten: { type: 'string' }
281
+ shorten: { type: 'string', description: '要删除的内容分享短码(不区分大小写)' }
242
282
  }
243
283
  },
244
- body: {
284
+ response: success('删除成功', {
245
285
  type: 'object',
246
- required: ['shorten'],
286
+ additionalProperties: true,
247
287
  properties: {
248
- shorten: { type: 'string' }
288
+ shorten: str('已删除的内容短码(大写)')
249
289
  }
250
- }
290
+ })
251
291
  }
252
292
  },
253
293
  async request => {
@@ -260,22 +300,19 @@ module.exports = fp(async (fastify, options) => {
260
300
  {
261
301
  onRequest: withEntryAuth,
262
302
  schema: {
303
+ tags: [TAG_FILE],
263
304
  summary: '创建文件夹',
264
- params: {
265
- type: 'object',
266
- required: ['shorten'],
267
- properties: {
268
- shorten: { type: 'string' }
269
- }
270
- },
305
+ description: `${ENTRY_HINT}。同级重名返回 400 DUPLICATE_NAME;新节点权限取站点 defaultPermission`,
306
+ params: shortenParams,
271
307
  body: {
272
308
  type: 'object',
273
309
  required: ['name'],
274
310
  properties: {
275
- parentId: { type: ['string', 'null'] },
276
- name: { type: 'string' }
311
+ parentId: { type: ['string', 'null'], description: PARENT_ID },
312
+ name: { type: 'string', description: '文件夹名称,去除首尾空格后不能为空' }
277
313
  }
278
- }
314
+ },
315
+ response: success('创建的文件夹', obj('文件夹节点', Object.assign({}, nodeBase, { children: list('子节点,新建时为空数组', obj('子节点')) })))
279
316
  }
280
317
  },
281
318
  async request => {
@@ -288,23 +325,20 @@ module.exports = fp(async (fastify, options) => {
288
325
  {
289
326
  onRequest: withEntryAuth,
290
327
  schema: {
328
+ tags: [TAG_FILE],
291
329
  summary: '创建文件',
292
- params: {
293
- type: 'object',
294
- required: ['shorten'],
295
- properties: {
296
- shorten: { type: 'string' }
297
- }
298
- },
330
+ description: `${ENTRY_HINT}。同级重名返回 400 DUPLICATE_NAME;新节点权限取站点 defaultPermission`,
331
+ params: shortenParams,
299
332
  body: {
300
333
  type: 'object',
301
334
  required: ['name'],
302
335
  properties: {
303
- parentId: { type: ['string', 'null'] },
304
- name: { type: 'string' },
305
- content: { type: 'string' }
336
+ parentId: { type: ['string', 'null'], description: PARENT_ID },
337
+ name: { type: 'string', description: '文件名称,去除首尾空格后不能为空' },
338
+ content: { type: 'string', description: '初始文件内容,默认空字符串' }
306
339
  }
307
- }
340
+ },
341
+ response: success('创建的文件', fileNode)
308
342
  }
309
343
  },
310
344
  async request => {
@@ -317,22 +351,26 @@ module.exports = fp(async (fastify, options) => {
317
351
  {
318
352
  onRequest: withEntryAuth,
319
353
  schema: {
354
+ tags: [TAG_FILE],
320
355
  summary: '保存文件内容',
321
- params: {
356
+ description: `${ENTRY_HINT}。只读节点返回 403,非文件返回 400`,
357
+ params: shortenParams,
358
+ body: {
322
359
  type: 'object',
323
- required: ['shorten'],
360
+ required: ['id'],
324
361
  properties: {
325
- shorten: { type: 'string' }
362
+ id: { type: 'string', description: '文件节点 ID' },
363
+ content: { type: 'string', description: '新的文件内容,不传保存为空字符串' }
326
364
  }
327
365
  },
328
- body: {
366
+ response: success('保存后的文件内容', {
329
367
  type: 'object',
330
- required: ['id'],
368
+ additionalProperties: true,
331
369
  properties: {
332
- id: { type: 'string' },
333
- content: { type: 'string' }
370
+ id: any('节点 ID(字符串)'),
371
+ content: str('保存后的文件内容')
334
372
  }
335
- }
373
+ })
336
374
  }
337
375
  },
338
376
  async request => {
@@ -345,22 +383,19 @@ module.exports = fp(async (fastify, options) => {
345
383
  {
346
384
  onRequest: withEntryAuth,
347
385
  schema: {
386
+ tags: [TAG_FILE],
348
387
  summary: '重命名节点',
349
- params: {
350
- type: 'object',
351
- required: ['shorten'],
352
- properties: {
353
- shorten: { type: 'string' }
354
- }
355
- },
388
+ description: `${ENTRY_HINT}。只读节点返回 403,同级重名返回 400 DUPLICATE_NAME`,
389
+ params: shortenParams,
356
390
  body: {
357
391
  type: 'object',
358
392
  required: ['id', 'name'],
359
393
  properties: {
360
- id: { type: 'string' },
361
- name: { type: 'string' }
394
+ id: { type: 'string', description: '文件或文件夹节点 ID' },
395
+ name: { type: 'string', description: '新名称,去除首尾空格后不能为空' }
362
396
  }
363
- }
397
+ },
398
+ response: success('重命名后的节点', fileNode)
364
399
  }
365
400
  },
366
401
  async request => {
@@ -373,22 +408,27 @@ module.exports = fp(async (fastify, options) => {
373
408
  {
374
409
  onRequest: withEntryAuth,
375
410
  schema: {
411
+ tags: [TAG_FILE],
376
412
  summary: '移动文件或文件夹到目标目录',
377
- params: {
378
- type: 'object',
379
- required: ['shorten'],
380
- properties: {
381
- shorten: { type: 'string' }
382
- }
383
- },
413
+ description: `${ENTRY_HINT}。目标与当前目录相同、移动到自身或自身子目录返回 400 INVALID_MOVE_TARGET;目标目录下重名返回 400 DUPLICATE_NAME`,
414
+ params: shortenParams,
384
415
  body: {
385
416
  type: 'object',
386
417
  required: ['id'],
387
418
  properties: {
388
- id: { type: 'string' },
389
- parentId: { type: ['string', 'null'] }
419
+ id: { type: 'string', description: '要移动的节点 ID' },
420
+ parentId: { type: ['string', 'null'], description: '目标文件夹节点 ID,为空表示移动到根目录' }
390
421
  }
391
- }
422
+ },
423
+ response: success(
424
+ '移动后的节点',
425
+ obj(
426
+ '节点',
427
+ Object.assign({}, nodeBase, {
428
+ parentId: any('新的父节点 ID,根目录为 null')
429
+ })
430
+ )
431
+ )
392
432
  }
393
433
  },
394
434
  async request => {
@@ -401,24 +441,28 @@ module.exports = fp(async (fastify, options) => {
401
441
  {
402
442
  onRequest: withEntryAuth,
403
443
  schema: {
444
+ tags: [TAG_FILE],
404
445
  summary: '删除节点',
405
- params: {
406
- type: 'object',
407
- required: ['shorten'],
408
- properties: {
409
- shorten: { type: 'string' }
410
- }
411
- },
446
+ description: `${ENTRY_HINT}。按顺序逐个删除,遇到只读节点(403)或非空文件夹(400)即中止,已删除的不回滚;删除文件时一并删除其内容分享短链`,
447
+ params: shortenParams,
412
448
  body: {
413
449
  type: 'object',
414
450
  required: ['ids'],
415
451
  properties: {
416
452
  ids: {
417
453
  type: 'array',
418
- items: { type: 'string' }
454
+ items: { type: 'string' },
455
+ description: '要删除的节点 ID 列表'
419
456
  }
420
457
  }
421
- }
458
+ },
459
+ response: success('删除成功', {
460
+ type: 'object',
461
+ additionalProperties: true,
462
+ properties: {
463
+ ids: list('已删除的节点 ID 列表', any('节点 ID(字符串)'))
464
+ }
465
+ })
422
466
  }
423
467
  },
424
468
  async request => {
@@ -1,4 +1,15 @@
1
1
  const fp = require('fastify-plugin');
2
+ const { num, list, success, empty, PERMISSION, SITE_STATUS, site } = require('../schema/response');
3
+
4
+ const TAG = 'Live组件站点管理后台-站点';
5
+
6
+ const siteOptions = {
7
+ type: 'object',
8
+ description: '站点扩展配置(JSON),原样保存',
9
+ properties: {
10
+ aiEnabled: { description: '是否开启 AI 生成,传 false 关闭;未传视为开启(需同时配置插件 ai.getAIModel)' }
11
+ }
12
+ };
2
13
 
3
14
  module.exports = fp(async (fastify, options) => {
4
15
  const { services } = fastify[options.name];
@@ -8,15 +19,33 @@ module.exports = fp(async (fastify, options) => {
8
19
  {
9
20
  onRequest: options.getAuthenticate('site'),
10
21
  schema: {
22
+ tags: [TAG],
11
23
  summary: '站点列表',
24
+ description: '分页查询站点,按创建时间倒序;可按状态与名称关键字筛选',
12
25
  query: {
13
26
  type: 'object',
14
27
  properties: {
15
- filter: { type: 'object', default: {} },
16
- perPage: { type: 'number', default: 20 },
17
- currentPage: { type: 'number', default: 1 }
28
+ filter: {
29
+ type: 'object',
30
+ default: {},
31
+ description: '筛选条件,如 filter[status]=open',
32
+ properties: {
33
+ status: { description: `按状态筛选,${SITE_STATUS};其它值忽略` },
34
+ keyword: { description: '按站点名称模糊匹配' }
35
+ }
36
+ },
37
+ perPage: { type: 'number', default: 20, description: '每页条数' },
38
+ currentPage: { type: 'number', default: 1, description: '当前页码,从 1 开始' }
39
+ }
40
+ },
41
+ response: success('站点分页列表', {
42
+ type: 'object',
43
+ additionalProperties: true,
44
+ properties: {
45
+ pageData: list('当前页站点列表', site),
46
+ totalCount: num('符合条件的站点总数')
18
47
  }
19
- }
48
+ })
20
49
  }
21
50
  },
22
51
  async request => {
@@ -29,14 +58,17 @@ module.exports = fp(async (fastify, options) => {
29
58
  {
30
59
  onRequest: options.getAuthenticate('site'),
31
60
  schema: {
61
+ tags: [TAG],
32
62
  summary: '站点详情',
63
+ description: '按 ID 获取站点信息,含公开入口地址 host;站点不存在返回 404',
33
64
  query: {
34
65
  type: 'object',
35
66
  required: ['id'],
36
67
  properties: {
37
- id: { type: 'string' }
68
+ id: { type: 'string', description: '站点 ID' }
38
69
  }
39
- }
70
+ },
71
+ response: success('站点详情', site)
40
72
  }
41
73
  },
42
74
  async request => {
@@ -49,17 +81,20 @@ module.exports = fp(async (fastify, options) => {
49
81
  {
50
82
  onRequest: options.getAuthenticate('site:create'),
51
83
  schema: {
84
+ tags: [TAG],
52
85
  summary: '创建站点并签发 shorten 链接',
86
+ description: '创建站点后通过 shorten 服务签发站点公开访问短码,返回带 host 的站点信息',
53
87
  body: {
54
88
  type: 'object',
55
89
  required: ['name'],
56
90
  properties: {
57
- name: { type: 'string' },
58
- defaultPermission: { type: 'string', enum: ['r', 'rw'] },
59
- status: { type: 'string', enum: ['open', 'closed'] },
60
- options: { type: 'object' }
91
+ name: { type: 'string', description: '站点名称,去除首尾空格后不能为空' },
92
+ defaultPermission: { type: 'string', enum: ['r', 'rw'], description: `新建节点默认${PERMISSION};默认 rw` },
93
+ status: { type: 'string', enum: ['open', 'closed'], description: `${SITE_STATUS};默认 open` },
94
+ options: siteOptions
61
95
  }
62
- }
96
+ },
97
+ response: success('创建后的站点', site)
63
98
  }
64
99
  },
65
100
  async request => {
@@ -72,18 +107,21 @@ module.exports = fp(async (fastify, options) => {
72
107
  {
73
108
  onRequest: options.getAuthenticate('site:manage'),
74
109
  schema: {
110
+ tags: [TAG],
75
111
  summary: '修改站点(含开启/关闭)',
112
+ description: '只更新传入的字段;shorten、时间字段不可修改。关闭后站点公开入口与内容短链返回 403,重新开启后沿用原 host',
76
113
  body: {
77
114
  type: 'object',
78
115
  required: ['id'],
79
116
  properties: {
80
- id: { type: 'string' },
81
- name: { type: 'string' },
82
- status: { type: 'string', enum: ['open', 'closed'] },
83
- defaultPermission: { type: 'string', enum: ['r', 'rw'] },
84
- options: { type: 'object' }
117
+ id: { type: 'string', description: '站点 ID' },
118
+ name: { type: 'string', description: '站点名称,传入时去除首尾空格后不能为空' },
119
+ status: { type: 'string', enum: ['open', 'closed'], description: SITE_STATUS },
120
+ defaultPermission: { type: 'string', enum: ['r', 'rw'], description: `新建节点默认${PERMISSION}` },
121
+ options: Object.assign({}, siteOptions, { description: '站点扩展配置(JSON),整体覆盖原值' })
85
122
  }
86
- }
123
+ },
124
+ response: empty('保存成功')
87
125
  }
88
126
  },
89
127
  async request => {
@@ -97,14 +135,17 @@ module.exports = fp(async (fastify, options) => {
97
135
  {
98
136
  onRequest: options.getAuthenticate('site:manage'),
99
137
  schema: {
138
+ tags: [TAG],
100
139
  summary: '删除站点',
140
+ description: '删除站点及其全部文件节点、内容分享记录,并移除站点短码与内容短码',
101
141
  body: {
102
142
  type: 'object',
103
143
  required: ['id'],
104
144
  properties: {
105
- id: { type: 'string' }
145
+ id: { type: 'string', description: '站点 ID' }
106
146
  }
107
- }
147
+ },
148
+ response: empty('删除成功')
108
149
  }
109
150
  },
110
151
  async request => {
@@ -0,0 +1,50 @@
1
+ // 响应 schema 会参与 fast-json-stringify 序列化:对象一律 additionalProperties: true 以免裁掉未声明字段;
2
+ // 用 nullable 而非类型数组(类型数组遇到不匹配的值会直接 500),类型不确定的字段(如 id、日期、JSON 列)只写说明不声明类型
3
+ const str = description => ({ type: 'string', nullable: true, description });
4
+ const num = description => ({ type: 'number', nullable: true, description });
5
+ const bool = description => ({ type: 'boolean', nullable: true, description });
6
+ const any = description => ({ description });
7
+ const list = (description, items) => ({ type: 'array', nullable: true, description, items });
8
+ const obj = (description, properties = {}) => ({ type: 'object', nullable: true, description, additionalProperties: true, properties });
9
+
10
+ const success = (description, schema) => ({
11
+ 200: Object.assign({}, schema || { type: 'object' }, { description })
12
+ });
13
+
14
+ const empty = (description = '操作成功,返回空对象') => success(description);
15
+
16
+ const PERMISSION = '权限:r 只读,rw 可读写';
17
+ const SITE_STATUS = '站点状态:open 开启,closed 关闭';
18
+
19
+ const site = obj('站点', {
20
+ id: any('站点 ID(字符串)'),
21
+ name: str('站点名称'),
22
+ shorten: str('站点公开访问短码'),
23
+ host: str('站点公开入口地址,由 getSiteHost(shorten) 生成,默认 {prefix}/{shorten}'),
24
+ status: str(SITE_STATUS),
25
+ defaultPermission: str(`新建节点默认${PERMISSION}`),
26
+ options: any('站点扩展配置(JSON),如 aiEnabled: false 关闭 AI'),
27
+ createdAt: any('创建时间'),
28
+ updatedAt: any('更新时间'),
29
+ deletedAt: any('删除时间,未删除为 null')
30
+ });
31
+
32
+ const nodeBase = {
33
+ id: any('节点 ID(字符串)'),
34
+ name: str('节点名称'),
35
+ type: str('节点类型:file 文件,directory 文件夹'),
36
+ permission: str(PERMISSION)
37
+ };
38
+
39
+ const treeNode = obj('文件树节点', Object.assign({}, nodeBase, { children: list('子节点列表(仅文件夹有),结构同本节点', obj('子节点')) }));
40
+
41
+ const contentShare = obj('内容分享短链', {
42
+ id: any('分享记录 ID(字符串)'),
43
+ nodeId: any('文件节点 ID(字符串)'),
44
+ shorten: str('内容短码(大写)'),
45
+ expires: any('过期时间,null 表示永久有效'),
46
+ url: str('内容访问地址:{getContentHost()}/content/{shorten},不带站点短码'),
47
+ createdAt: any('创建时间')
48
+ });
49
+
50
+ module.exports = { str, num, bool, any, list, obj, success, empty, PERMISSION, SITE_STATUS, site, nodeBase, treeNode, contentShare };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kne/fastify-live-components-site",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Fastify 插件:为 LiveComponentEditor 提供远程站点文件树与短链访问管理",
5
5
  "main": "index.js",
6
6
  "scripts": {