@kne/fastify-app-manager 0.1.7 → 0.1.8

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.
package/README.md CHANGED
@@ -193,6 +193,7 @@ await fastify.listen({ port: 3000 });
193
193
  | `dbQueryMaxRows` | number | 否 | `500` | 只读 query SQL 最大返回行数 |
194
194
  | `pm2Defaults` | object | 否 | 见下表 | 全局 PM2 默认;可被应用 `pm2Config` 覆盖 |
195
195
  | `createAuthenticate` | function | 否 | 见说明 | 返回 `onRequest` 钩子数组;默认尝试 admin |
196
+ | `createUserAuthenticate` | function | 否 | 见说明 | 应用中心等普通用户接口的 `onRequest` 钩子数组;默认尝试 `fastify.account.authenticate.user` |
196
197
  | `healthCheckPath` | string | 否 | `'/'` | 部署后就绪探测路径 |
197
198
  | `healthCheckTimeoutMs` | number | 否 | `30000` | 探测总超时(毫秒) |
198
199
  | `healthCheckIntervalMs` | number | 否 | `1000` | 探测轮询间隔(毫秒) |
@@ -240,11 +241,13 @@ await fastify.listen({ port: 3000 });
240
241
  | domain | string | 否 | - | 绑定 Host(唯一);冲突返回 409 |
241
242
  | icon | string | 否 | - | 图标 |
242
243
  | description | string | 否 | - | 描述 |
244
+ | category | object / string | 否 | - | 应用分类(应用中心分组用),写入 `options.category`;推荐传 fastify-group 分组对象,对象只保留 `{ code, name }`,字符串原样保存 |
245
+ | isPublic | boolean | 否 | `true` | 是否在公开应用中心展示,写入 `options.isPublic`;未设置视为公开 |
243
246
  | env | object | 否 | `{}` | 应用自有环境变量 |
244
247
  | pm2Config | object | 否 | `{}` | PM2 覆盖项 |
245
248
  | options | object | 否 | `{}` | 扩展字段;可含 `secretEnvKeys: string[]` 显式密钥键名 |
246
249
 
247
- 返回脱敏后的应用对象(含 `port`、`pathUrl`、`passthroughEnvKeys`、`secretEnvKeys`、`status: 'idle'` 等)。
250
+ 返回脱敏后的应用对象(含 `port`、`pathUrl`、`category`、`passthroughEnvKeys`、`secretEnvKeys`、`status: 'idle'` 等)。
248
251
 
249
252
  ##### POST `{prefix}/app/save`
250
253
 
@@ -254,6 +257,8 @@ await fastify.listen({ port: 3000 });
254
257
  |--------|------|------|------|
255
258
  | name | string | 是 | 应用 slug |
256
259
  | label / domain / icon / description / env / pm2Config / options | - | 否 | 传入的字段才更新;`env` 按 patch 语义合并 |
260
+ | category | object / string / null | 否 | 合并写入 `options.category`(对象只保留 `{ code, name }`),不影响 `options` 其它键;传空值清除分类 |
261
+ | isPublic | boolean | 否 | 合并写入 `options.isPublic`,不影响 `options` 其它键 |
257
262
 
258
263
  ##### POST `{prefix}/app/save-env`
259
264
 
@@ -397,6 +402,31 @@ await fastify.listen({ port: 3000 });
397
402
 
398
403
  返回 `{ pageData, totalCount }`,元素均为脱敏后的应用对象。
399
404
 
405
+ ##### GET `{prefix}/app/center/list`
406
+
407
+ 应用中心列表,使用 `createUserAuthenticate` 鉴权(普通登录用户可访问)。仅返回 `status: 'running'` 的应用,且只含公开字段(不含 env、端口、路径等)。
408
+
409
+ ```json
410
+ {
411
+ "pageData": [
412
+ {
413
+ "name": "demo",
414
+ "label": "Demo",
415
+ "icon": "file-id",
416
+ "description": "d",
417
+ "category": { "code": "tools", "name": "工具" },
418
+ "isPublic": true,
419
+ "pathUrl": "/app/demo/"
420
+ }
421
+ ],
422
+ "totalCount": 1
423
+ }
424
+ ```
425
+
426
+ ##### GET `{prefix}/app/center/public-list`
427
+
428
+ 公开应用中心列表,**无需登录**。返回结构同 `app/center/list`,仅包含运行中且 `isPublic` 不为 `false` 的应用。
429
+
400
430
  ##### GET `{prefix}/app/detail`
401
431
 
402
432
  | 参数名 | 类型 | 必填 | 说明 |
@@ -527,7 +557,7 @@ data: {"appName":"demo","stream":"out","content":"...","line":1}
527
557
  | description | string / null | 描述 |
528
558
  | env | object | 自有环境变量 |
529
559
  | pm2Config | object | PM2 覆盖 |
530
- | options | object | 扩展字段;`secretEnvKeys` 为显式密钥键名列表 |
560
+ | options | object | 扩展字段;`secretEnvKeys` 为显式密钥键名列表,`category` 为应用分类,`isPublic` 为是否公开(未设置视为公开) |
531
561
  | port | number | 分配端口 |
532
562
  | status | string | `idle` / `deploying` / `running` / `stopped` / `error` |
533
563
  | currentVersionId | string / null | 当前部署版本 id |
package/index.js CHANGED
@@ -54,6 +54,13 @@ module.exports = fp(
54
54
  return [fastify.account.authenticate.admin];
55
55
  }
56
56
  return [];
57
+ },
58
+ /** 普通登录用户可访问的接口(应用中心列表) */
59
+ createUserAuthenticate: () => {
60
+ if (fastify.account?.authenticate?.user) {
61
+ return [fastify.account.authenticate.user];
62
+ }
63
+ return [];
57
64
  }
58
65
  },
59
66
  options
@@ -4,6 +4,7 @@ const { createSseReply } = require('../utils/logStream');
4
4
  module.exports = fp(async (fastify, options) => {
5
5
  const { services } = fastify[options.name];
6
6
  const auth = () => options.createAuthenticate();
7
+ const userAuth = () => options.createUserAuthenticate();
7
8
 
8
9
  fastify.post(
9
10
  `${options.prefix}/app/create`,
@@ -20,6 +21,8 @@ module.exports = fp(async (fastify, options) => {
20
21
  domain: { type: 'string' },
21
22
  icon: { type: 'string' },
22
23
  description: { type: 'string' },
24
+ category: { type: ['object', 'string', 'null'] },
25
+ isPublic: { type: 'boolean' },
23
26
  env: { type: 'object' },
24
27
  pm2Config: { type: 'object' },
25
28
  options: { type: 'object' }
@@ -45,6 +48,8 @@ module.exports = fp(async (fastify, options) => {
45
48
  domain: { type: 'string' },
46
49
  icon: { type: 'string' },
47
50
  description: { type: 'string' },
51
+ category: { type: ['object', 'string', 'null'] },
52
+ isPublic: { type: 'boolean' },
48
53
  env: { type: 'object' },
49
54
  pm2Config: { type: 'object' },
50
55
  options: { type: 'object' }
@@ -55,6 +60,27 @@ module.exports = fp(async (fastify, options) => {
55
60
  async request => services.app.save(request.body)
56
61
  );
57
62
 
63
+ fastify.get(
64
+ `${options.prefix}/app/center/list`,
65
+ {
66
+ onRequest: userAuth(),
67
+ schema: {
68
+ summary: '应用中心:运行中应用的公开信息(普通登录用户可访问)'
69
+ }
70
+ },
71
+ async () => services.app.centerList()
72
+ );
73
+
74
+ fastify.get(
75
+ `${options.prefix}/app/center/public-list`,
76
+ {
77
+ schema: {
78
+ summary: '应用中心:运行中且公开的应用(无需登录)'
79
+ }
80
+ },
81
+ async () => services.app.centerList({ publicOnly: true })
82
+ );
83
+
58
84
  fastify.post(
59
85
  `${options.prefix}/app/save-env`,
60
86
  {
@@ -31,6 +31,18 @@ module.exports = fp(async (fastify, options) => {
31
31
  const mountPrefix = name => `${options.pathPrefix.replace(/\/$/, '')}/${name}`;
32
32
 
33
33
  const resolveSecretEnvKeys = appLike => normalizeSecretKeys(appLike?.options?.secretEnvKeys);
34
+ // 未设置视为公开(与远程组件默认公开一致)
35
+ const isAppPublic = appLike => appLike?.options?.isPublic !== false;
36
+ // 分组选择器的值可能是整条分组记录(含 id/children/options),只保留 code/name
37
+ const normalizeCategory = category => {
38
+ if (!category) {
39
+ return null;
40
+ }
41
+ if (typeof category !== 'object') {
42
+ return String(category);
43
+ }
44
+ return category.code ? { code: category.code, name: category.name || category.code } : null;
45
+ };
34
46
 
35
47
  const toPublicApp = app => {
36
48
  if (!app) {
@@ -43,6 +55,8 @@ module.exports = fp(async (fastify, options) => {
43
55
  json.passthroughEnvKeys = options.passthroughEnvKeys || [];
44
56
  json.secretEnvKeys = collectSecretKeys(json.env || {}, envOpts);
45
57
  json.pathUrl = `${mountPrefix(json.name)}/`;
58
+ json.category = json.options?.category || null;
59
+ json.isPublic = isAppPublic(json);
46
60
  return json;
47
61
  };
48
62
 
@@ -55,7 +69,7 @@ module.exports = fp(async (fastify, options) => {
55
69
  };
56
70
 
57
71
  const create = async data => {
58
- const { name, label, domain, icon, description, env, pm2Config, options: appOptions } = data;
72
+ const { name, label, domain, icon, description, category, isPublic, env, pm2Config, options: appOptions } = data;
59
73
  if (!name || !NAME_RE.test(name)) {
60
74
  httpError(400, 'invalid name: use lowercase slug [a-z0-9-]');
61
75
  }
@@ -89,6 +103,12 @@ module.exports = fp(async (fastify, options) => {
89
103
  if (normalizedOptions.secretEnvKeys !== undefined) {
90
104
  normalizedOptions.secretEnvKeys = normalizeSecretKeys(normalizedOptions.secretEnvKeys);
91
105
  }
106
+ if (category !== undefined) {
107
+ normalizedOptions.category = normalizeCategory(category);
108
+ }
109
+ if (isPublic !== undefined) {
110
+ normalizedOptions.isPublic = !!isPublic;
111
+ }
92
112
 
93
113
  const app = await models.app.create({
94
114
  name,
@@ -108,7 +128,7 @@ module.exports = fp(async (fastify, options) => {
108
128
  return toPublicApp(app);
109
129
  };
110
130
 
111
- const save = async ({ name, ...data }) => {
131
+ const save = async ({ name, category, isPublic, ...data }) => {
112
132
  const app = await getByName(name);
113
133
  const omit = ['name', 'port', 'rootPath', 'pm2Name', 'status', 'currentVersionId'];
114
134
  const patch = {};
@@ -133,6 +153,12 @@ module.exports = fp(async (fastify, options) => {
133
153
  secretEnvKeys: normalizeSecretKeys(patch.options.secretEnvKeys)
134
154
  });
135
155
  }
156
+ if (category !== undefined) {
157
+ patch.options = Object.assign({}, patch.options || app.options || {}, { category: normalizeCategory(category) });
158
+ }
159
+ if (isPublic !== undefined) {
160
+ patch.options = Object.assign({}, patch.options || app.options || {}, { isPublic: !!isPublic });
161
+ }
136
162
  await app.update(patch);
137
163
  return toPublicApp(app);
138
164
  };
@@ -172,6 +198,22 @@ module.exports = fp(async (fastify, options) => {
172
198
  };
173
199
  };
174
200
 
201
+ const centerList = async ({ publicOnly = false } = {}) => {
202
+ const rows = await models.app.findAll({ where: { status: 'running' }, order: [['createdAt', 'DESC']] });
203
+ const pageData = rows
204
+ .filter(app => !publicOnly || isAppPublic(app))
205
+ .map(app => ({
206
+ name: app.name,
207
+ label: app.label,
208
+ icon: app.icon,
209
+ description: app.description,
210
+ category: app.options?.category || null,
211
+ isPublic: isAppPublic(app),
212
+ pathUrl: `${mountPrefix(app.name)}/`
213
+ }));
214
+ return { pageData, totalCount: pageData.length };
215
+ };
216
+
175
217
  const detail = async ({ name }) => {
176
218
  const app = await getByName(name);
177
219
  return toPublicApp(app);
@@ -653,6 +695,7 @@ module.exports = fp(async (fastify, options) => {
653
695
  save,
654
696
  saveEnv,
655
697
  list,
698
+ centerList,
656
699
  detail,
657
700
  uploadVersion,
658
701
  listVersions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kne/fastify-app-manager",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Fastify 单机应用市场插件:版本上传、PM2 托管、路径/域名反代与日志 SSE",
5
5
  "main": "index.js",
6
6
  "scripts": {