关于login重构
This commit is contained in:
parent
0179fe73a3
commit
8053a3db64
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "submodules/code-center-module"]
|
||||||
|
path = submodules/code-center-module
|
||||||
|
url = git@git.xiongxiao.me:kevisual/code-center-module.git
|
16
package.json
16
package.json
@ -21,7 +21,8 @@
|
|||||||
"start": "pm2 start dist/app.mjs --name codecenter",
|
"start": "pm2 start dist/app.mjs --name codecenter",
|
||||||
"release": "node ./config/release/index.mjs",
|
"release": "node ./config/release/index.mjs",
|
||||||
"pub": "envision pack -p -u",
|
"pub": "envision pack -p -u",
|
||||||
"ssl": "ssl -L 6379:localhost:6379 light "
|
"ssh": "ssh -L 6379:localhost:6379 light ",
|
||||||
|
"ssh:sky": "ssh -L 6379:172.21.32.13:6379 sky"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"types": "types/index.d.ts",
|
"types": "types/index.d.ts",
|
||||||
@ -32,7 +33,6 @@
|
|||||||
],
|
],
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kevisual/auth": "1.0.5",
|
|
||||||
"@kevisual/local-app-manager": "0.1.9",
|
"@kevisual/local-app-manager": "0.1.9",
|
||||||
"@kevisual/router": "0.0.9",
|
"@kevisual/router": "0.0.9",
|
||||||
"@kevisual/use-config": "^1.0.9",
|
"@kevisual/use-config": "^1.0.9",
|
||||||
@ -46,10 +46,10 @@
|
|||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"minio": "^8.0.5",
|
"minio": "^8.0.5",
|
||||||
"nanoid": "^5.1.3",
|
"nanoid": "^5.1.5",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
"pg": "^8.14.0",
|
"pg": "^8.14.1",
|
||||||
"pm2": "^6.0.5",
|
"pm2": "^6.0.5",
|
||||||
"rollup-plugin-esbuild": "^6.2.1",
|
"rollup-plugin-esbuild": "^6.2.1",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/code-center-module": "0.0.13",
|
"@kevisual/code-center-module": "workspace:*",
|
||||||
"@kevisual/types": "^0.0.6",
|
"@kevisual/types": "^0.0.6",
|
||||||
"@rollup/plugin-alias": "^5.1.1",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-commonjs": "^28.0.3",
|
"@rollup/plugin-commonjs": "^28.0.3",
|
||||||
@ -75,15 +75,15 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^22.13.10",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.12",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"nodemon": "^3.1.9",
|
"nodemon": "^3.1.9",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.35.0",
|
"rollup": "^4.36.0",
|
||||||
"rollup-plugin-copy": "^3.5.0",
|
"rollup-plugin-copy": "^3.5.0",
|
||||||
"rollup-plugin-dts": "^6.1.1",
|
"rollup-plugin-dts": "^6.2.0",
|
||||||
"tape": "^5.9.0",
|
"tape": "^5.9.0",
|
||||||
"tsx": "^4.19.3",
|
"tsx": "^4.19.3",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
|
562
pnpm-lock.yaml
generated
562
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- 'submodules/*'
|
@ -1,14 +1,9 @@
|
|||||||
import './routes/index.ts';
|
import './routes/index.ts';
|
||||||
import { app } from './app.ts';
|
import { app } from './app.ts';
|
||||||
import { useConfig } from '@kevisual/use-config';
|
|
||||||
import { User } from './models/user.ts';
|
import { User } from './models/user.ts';
|
||||||
import { createAuthRoute } from '@kevisual/auth';
|
import { addAuth } from '@kevisual/code-center-module/models';
|
||||||
const config = useConfig<{ tokenSecret: string }>();
|
|
||||||
|
|
||||||
createAuthRoute({
|
addAuth(app);
|
||||||
app,
|
|
||||||
secret: config.tokenSecret,
|
|
||||||
});
|
|
||||||
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
|
@ -7,8 +7,6 @@ import { useFileStore } from '@kevisual/use-config/file-store';
|
|||||||
import { app, minioClient } from '@/app.ts';
|
import { app, minioClient } from '@/app.ts';
|
||||||
import { bucketName } from '@/modules/minio.ts';
|
import { bucketName } from '@/modules/minio.ts';
|
||||||
import { getContentType } from '@/utils/get-content-type.ts';
|
import { getContentType } from '@/utils/get-content-type.ts';
|
||||||
import { hash } from 'crypto';
|
|
||||||
import { MicroAppUploadModel } from '@/routes/micro-app/models.ts';
|
|
||||||
const cacheFilePath = useFileStore('cache-file', { needExists: true });
|
const cacheFilePath = useFileStore('cache-file', { needExists: true });
|
||||||
|
|
||||||
router.post('/api/micro-app/upload', async (req, res) => {
|
router.post('/api/micro-app/upload', async (req, res) => {
|
||||||
@ -119,70 +117,7 @@ router.post('/api/micro-app/upload', async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/api/micro-app/download/:id', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
|
|
||||||
res.end(error('Key parameter is required'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const query = new URL(req.url || '', 'http://localhost');
|
|
||||||
const notNeedToken = query.searchParams.get('notNeedToken') || '';
|
|
||||||
const fileTitle = query.searchParams.get('title') || '';
|
|
||||||
if (res.headersSent) return; // 如果响应已发送,不再处理
|
|
||||||
let tokenUser;
|
|
||||||
if (!DEV_SERVER && !notNeedToken) {
|
|
||||||
const auth = await checkAuth(req, res);
|
|
||||||
tokenUser = auth.tokenUser;
|
|
||||||
if (!tokenUser) return;
|
|
||||||
}
|
|
||||||
let file: MicroAppUploadModel | null = null;
|
|
||||||
if (!DEV_SERVER) {
|
|
||||||
// file.uid !== tokenUser.id && res.end(error('No permission', 403));
|
|
||||||
// return;
|
|
||||||
}
|
|
||||||
if (fileTitle) {
|
|
||||||
file = await MicroAppUploadModel.findOne({
|
|
||||||
where: { title: fileTitle },
|
|
||||||
});
|
|
||||||
} else if (id) {
|
|
||||||
file = await MicroAppUploadModel.findByPk(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
res.end(error('File not found'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const objectName = file.data?.file?.path;
|
|
||||||
const fileName = file.data?.file?.name;
|
|
||||||
if (!objectName) {
|
|
||||||
res.end(error('File not found'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
|
|
||||||
res.setHeader('app-key', file.data?.key || id);
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
|
|
||||||
try {
|
|
||||||
const stream = await minioClient.getObject(bucketName, objectName);
|
|
||||||
// 捕获流的错误,防止崩溃
|
|
||||||
stream.on('error', (err) => {
|
|
||||||
console.error('Error while streaming file:', err);
|
|
||||||
if (!res.headersSent) {
|
|
||||||
res.end(error('Error downloading file'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.pipe(res).on('finish', () => {
|
|
||||||
console.log(`File download completed: ${id}`);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error during download:', err);
|
|
||||||
if (!res.headersSent) {
|
|
||||||
res.end(error('Error downloading file'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
function parseIfJson(collection: any): any {
|
function parseIfJson(collection: any): any {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(collection);
|
return JSON.parse(collection);
|
||||||
|
@ -15,8 +15,9 @@ export const validateDirectory = (directory?: string) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 把directory的/替换掉后,只能包含数字、字母、下划线、中划线
|
// 把directory的/替换掉后,只能包含数字、字母、下划线、中划线
|
||||||
|
// 可以包含.
|
||||||
let _directory = directory?.replace(/\//g, '');
|
let _directory = directory?.replace(/\//g, '');
|
||||||
if (_directory && !/^[a-zA-Z0-9_-]+$/.test(_directory)) {
|
if (_directory && !/^[a-zA-Z0-9_.-]+$/.test(_directory)) {
|
||||||
return {
|
return {
|
||||||
code: 500,
|
code: 500,
|
||||||
message: 'directory is invalid, only number, letter, underline and hyphen are allowed',
|
message: 'directory is invalid, only number, letter, underline and hyphen are allowed',
|
||||||
|
@ -176,7 +176,7 @@ app
|
|||||||
uid,
|
uid,
|
||||||
version: version || '0.0.0',
|
version: version || '0.0.0',
|
||||||
title: appKey,
|
title: appKey,
|
||||||
proxy: true,
|
proxy: appKey.includes('center') ? false : true,
|
||||||
data: {
|
data: {
|
||||||
files: files || [],
|
files: files || [],
|
||||||
},
|
},
|
||||||
@ -352,7 +352,7 @@ app
|
|||||||
user: checkUsername,
|
user: checkUsername,
|
||||||
uid,
|
uid,
|
||||||
data: { files: needAddFiles },
|
data: { files: needAddFiles },
|
||||||
proxy: true,
|
proxy: appKey.includes('center') ? false : true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const appModel = await AppModel.findOne({ where: { key: appKey, version, uid } });
|
const appModel = await AppModel.findOne({ where: { key: appKey, version, uid } });
|
||||||
|
@ -18,8 +18,9 @@ export interface AppData {
|
|||||||
password?: string; // 受保护的访问密码
|
password?: string; // 受保护的访问密码
|
||||||
'expiration-time'?: string; // 受保护的访问过期时间
|
'expiration-time'?: string; // 受保护的访问过期时间
|
||||||
};
|
};
|
||||||
|
// 运行环境,browser, node, 或者其他,是数组
|
||||||
|
runtime?: string[];
|
||||||
}
|
}
|
||||||
export type AppType = 'web-single' | 'web-module'; // 可以做到网页代理
|
|
||||||
export enum AppStatus {
|
export enum AppStatus {
|
||||||
running = 'running',
|
running = 'running',
|
||||||
stop = 'stop',
|
stop = 'stop',
|
||||||
@ -36,9 +37,7 @@ export class AppModel extends Model {
|
|||||||
declare description: string;
|
declare description: string;
|
||||||
declare version: string;
|
declare version: string;
|
||||||
declare domain: string;
|
declare domain: string;
|
||||||
declare appType: string;
|
|
||||||
declare key: string;
|
declare key: string;
|
||||||
declare type: string;
|
|
||||||
declare uid: string;
|
declare uid: string;
|
||||||
declare pid: string;
|
declare pid: string;
|
||||||
// 是否是history路由代理模式。静态的直接转minio,而不需要缓存下来。
|
// 是否是history路由代理模式。静态的直接转minio,而不需要缓存下来。
|
||||||
@ -74,18 +73,10 @@ AppModel.init(
|
|||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
},
|
},
|
||||||
appType: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
key: {
|
key: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
// 和 uid 组合唯一
|
// 和 uid 组合唯一
|
||||||
},
|
},
|
||||||
type: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
uid: {
|
uid: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
@ -2,6 +2,7 @@ import { app } from '@/app.ts';
|
|||||||
import { AppModel } from '../module/index.ts';
|
import { AppModel } from '../module/index.ts';
|
||||||
|
|
||||||
// curl http://localhost:4005/api/router?path=app&key=public-list
|
// curl http://localhost:4005/api/router?path=app&key=public-list
|
||||||
|
// TODO:
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
path: 'app',
|
path: 'app',
|
||||||
@ -12,6 +13,9 @@ app
|
|||||||
where: {
|
where: {
|
||||||
status: 'running',
|
status: 'running',
|
||||||
},
|
},
|
||||||
|
// attributes: {
|
||||||
|
// exclude: ['data'],
|
||||||
|
// },
|
||||||
logging: false,
|
logging: false,
|
||||||
});
|
});
|
||||||
ctx.body = list;
|
ctx.body = list;
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import { CustomError } from '@kevisual/router';
|
|
||||||
import { AppModel, AppListModel } from './module/index.ts';
|
import { AppModel, AppListModel } from './module/index.ts';
|
||||||
import { app } from '@/app.ts';
|
import { app } from '@/app.ts';
|
||||||
import { setExpire } from './revoke.ts';
|
import { setExpire } from './revoke.ts';
|
||||||
import { getMinioListAndSetToAppList } from '../file/index.ts';
|
|
||||||
import { getUidByUsername } from './util.ts';
|
|
||||||
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
@ -18,6 +15,9 @@ app
|
|||||||
where: {
|
where: {
|
||||||
uid: tokenUser.id,
|
uid: tokenUser.id,
|
||||||
},
|
},
|
||||||
|
attributes: {
|
||||||
|
exclude: ['data'],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
ctx.body = list;
|
ctx.body = list;
|
||||||
return ctx;
|
return ctx;
|
||||||
@ -29,24 +29,25 @@ app
|
|||||||
path: 'user-app',
|
path: 'user-app',
|
||||||
key: 'get',
|
key: 'get',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
|
description: '获取用户应用,可以指定id或者key',
|
||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
const id = ctx.query.id;
|
const id = ctx.query.id;
|
||||||
const { key } = ctx.query.data || {};
|
const { key } = ctx.query.data || {};
|
||||||
if (!id && !key) {
|
if (!id && !key) {
|
||||||
throw new CustomError('id is required');
|
ctx.throw(500, 'id is required');
|
||||||
}
|
}
|
||||||
if (id) {
|
if (id) {
|
||||||
const am = await AppModel.findByPk(id);
|
const am = await AppModel.findByPk(id);
|
||||||
if (!am) {
|
if (!am) {
|
||||||
throw new CustomError('app not found');
|
ctx.throw(500, 'app not found');
|
||||||
}
|
}
|
||||||
ctx.body = am;
|
ctx.body = am;
|
||||||
} else {
|
} else {
|
||||||
const am = await AppModel.findOne({ where: { key, uid: tokenUser.id } });
|
const am = await AppModel.findOne({ where: { key, uid: tokenUser.id } });
|
||||||
if (!am) {
|
if (!am) {
|
||||||
throw new CustomError('app not found');
|
ctx.throw(500, 'app not found');
|
||||||
}
|
}
|
||||||
ctx.body = am;
|
ctx.body = am;
|
||||||
}
|
}
|
||||||
@ -75,16 +76,16 @@ app
|
|||||||
setExpire(newApp.key, app.user);
|
setExpire(newApp.key, app.user);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new CustomError('app not found');
|
ctx.throw(500, 'app not found');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!rest.key) {
|
if (!rest.key) {
|
||||||
throw new CustomError('key is required');
|
ctx.throw(500, 'key is required');
|
||||||
}
|
}
|
||||||
const findApp = await AppModel.findOne({ where: { key: rest.key, uid: tokenUser.id } });
|
const findApp = await AppModel.findOne({ where: { key: rest.key, uid: tokenUser.id } });
|
||||||
if (findApp) {
|
if (findApp) {
|
||||||
throw new CustomError('key already exists');
|
ctx.throw(500, 'key already exists');
|
||||||
}
|
}
|
||||||
const app = await AppModel.create({
|
const app = await AppModel.create({
|
||||||
data: { files: [] },
|
data: { files: [] },
|
||||||
@ -107,14 +108,14 @@ app
|
|||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
const id = ctx.query.id;
|
const id = ctx.query.id;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new CustomError('id is required');
|
ctx.throw(500, 'id is required');
|
||||||
}
|
}
|
||||||
const am = await AppModel.findByPk(id);
|
const am = await AppModel.findByPk(id);
|
||||||
if (!am) {
|
if (!am) {
|
||||||
throw new CustomError('app not found');
|
ctx.throw(500, 'app not found');
|
||||||
}
|
}
|
||||||
if (am.uid !== tokenUser.id) {
|
if (am.uid !== tokenUser.id) {
|
||||||
throw new CustomError('app not found');
|
ctx.throw(500, 'app not found');
|
||||||
}
|
}
|
||||||
const list = await AppListModel.findAll({ where: { key: am.key, uid: tokenUser.id } });
|
const list = await AppListModel.findAll({ where: { key: am.key, uid: tokenUser.id } });
|
||||||
await am.destroy({ force: true });
|
await am.destroy({ force: true });
|
||||||
@ -133,11 +134,11 @@ app
|
|||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const id = ctx.query.id;
|
const id = ctx.query.id;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new CustomError('id is required');
|
ctx.throw(500, 'id is required');
|
||||||
}
|
}
|
||||||
const am = await AppListModel.findByPk(id);
|
const am = await AppListModel.findByPk(id);
|
||||||
if (!am) {
|
if (!am) {
|
||||||
throw new CustomError('app not found');
|
ctx.throw(500, 'app not found');
|
||||||
}
|
}
|
||||||
const amJson = am.toJSON();
|
const amJson = am.toJSON();
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
@ -146,5 +147,3 @@ app
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
import './list.ts';
|
import './list.ts';
|
||||||
import './upload-config.ts';
|
import './upload-config.ts';
|
||||||
|
import './share-config.ts';
|
@ -1,5 +1,6 @@
|
|||||||
import { app } from '@/app.ts';
|
import { app } from '@/app.ts';
|
||||||
import { ConfigModel } from './models/model.ts';
|
import { ConfigModel } from './models/model.ts';
|
||||||
|
import { ShareConfigService } from './services/share.ts';
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
path: 'config',
|
path: 'config',
|
||||||
@ -12,6 +13,7 @@ app
|
|||||||
where: {
|
where: {
|
||||||
uid: id,
|
uid: id,
|
||||||
},
|
},
|
||||||
|
order: [['updatedAt', 'DESC']],
|
||||||
});
|
});
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
list: config,
|
list: config,
|
||||||
@ -19,3 +21,123 @@ app
|
|||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'config',
|
||||||
|
key: 'update',
|
||||||
|
middleware: ['auth'],
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const tokernUser = ctx.state.tokenUser;
|
||||||
|
const tuid = tokernUser.id;
|
||||||
|
const { id, key, data, ...rest } = ctx.query?.data || {};
|
||||||
|
if (id) {
|
||||||
|
const config = await ConfigModel.findByPk(id);
|
||||||
|
if (config && config.uid === tuid) {
|
||||||
|
const keyConfig = await ConfigModel.findOne({
|
||||||
|
where: {
|
||||||
|
key,
|
||||||
|
uid: tuid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (keyConfig && keyConfig.id !== id) {
|
||||||
|
ctx.throw(403, 'key is already exists');
|
||||||
|
}
|
||||||
|
await config.update({
|
||||||
|
key,
|
||||||
|
data: {
|
||||||
|
...config.data,
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
...rest,
|
||||||
|
});
|
||||||
|
if (config.data?.permission?.share === 'public') {
|
||||||
|
await ShareConfigService.expireShareConfig(config.key, tokernUser.username);
|
||||||
|
}
|
||||||
|
ctx.body = config;
|
||||||
|
} else {
|
||||||
|
ctx.throw(403, 'no permission');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const keyConfig = await ConfigModel.findOne({
|
||||||
|
where: {
|
||||||
|
key,
|
||||||
|
uid: tuid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (keyConfig) {
|
||||||
|
ctx.throw(403, 'key is already exists');
|
||||||
|
}
|
||||||
|
const config = await ConfigModel.create({
|
||||||
|
key,
|
||||||
|
...rest,
|
||||||
|
data: data,
|
||||||
|
uid: tuid,
|
||||||
|
});
|
||||||
|
ctx.body = config;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'config',
|
||||||
|
key: 'get',
|
||||||
|
middleware: ['auth'],
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const tokernUser = ctx.state.tokenUser;
|
||||||
|
const tuid = tokernUser.id;
|
||||||
|
const { id, key } = ctx.query?.data || {};
|
||||||
|
if (!id && !key) {
|
||||||
|
ctx.throw(400, 'id or key is required');
|
||||||
|
}
|
||||||
|
let config: ConfigModel;
|
||||||
|
if (id) {
|
||||||
|
config = await ConfigModel.findByPk(id);
|
||||||
|
}
|
||||||
|
if (!config && key) {
|
||||||
|
config = await ConfigModel.findOne({
|
||||||
|
where: {
|
||||||
|
key,
|
||||||
|
uid: tuid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!config) {
|
||||||
|
ctx.throw(404, 'config not found');
|
||||||
|
}
|
||||||
|
if (config && config.uid === tuid) {
|
||||||
|
ctx.body = config;
|
||||||
|
} else {
|
||||||
|
ctx.throw(403, 'no permission');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'config',
|
||||||
|
key: 'delete',
|
||||||
|
middleware: ['auth'],
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const tokernUser = ctx.state.tokenUser;
|
||||||
|
const tuid = tokernUser.id;
|
||||||
|
const { id } = ctx.query?.data || {};
|
||||||
|
if (id) {
|
||||||
|
const config = await ConfigModel.findOne({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (config && config.uid === tuid) {
|
||||||
|
await config.destroy();
|
||||||
|
} else {
|
||||||
|
ctx.throw(403, 'no permission');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.throw(400, 'id is required');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
@ -5,6 +5,9 @@ import { DataTypes, Model } from 'sequelize';
|
|||||||
export interface ConfigData {
|
export interface ConfigData {
|
||||||
key?: string;
|
key?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
permission?: {
|
||||||
|
share?: 'public' | 'private';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Config = Partial<InstanceType<typeof ConfigModel>>;
|
export type Config = Partial<InstanceType<typeof ConfigModel>>;
|
||||||
@ -70,7 +73,6 @@ export class ConfigModel extends Model {
|
|||||||
static async getUploadConfig(opts: { uid: string }) {
|
static async getUploadConfig(opts: { uid: string }) {
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
key: 'upload',
|
key: 'upload',
|
||||||
type: 'upload',
|
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
};
|
};
|
||||||
const config = await ConfigModel.getConfig('upload', {
|
const config = await ConfigModel.getConfig('upload', {
|
||||||
|
41
src/routes/config/services/share.ts
Normal file
41
src/routes/config/services/share.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { ConfigModel } from '../models/model.ts';
|
||||||
|
import { CustomError } from '@kevisual/router';
|
||||||
|
import { redis } from '@/app.ts';
|
||||||
|
import { User } from '@/models/user.ts';
|
||||||
|
export class ShareConfigService extends ConfigModel {
|
||||||
|
/**
|
||||||
|
* 获取分享的配置
|
||||||
|
* @param key 配置的key
|
||||||
|
* @param username 分享者的username
|
||||||
|
* @returns 配置
|
||||||
|
*/
|
||||||
|
static async getShareConfig(key: string, username: string) {
|
||||||
|
const shareCacheConfig = await redis.get(`config:share:${username}:${key}`);
|
||||||
|
if (shareCacheConfig) {
|
||||||
|
return JSON.parse(shareCacheConfig);
|
||||||
|
}
|
||||||
|
const user = await User.findOne({
|
||||||
|
where: { username },
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
throw new CustomError(404, 'user not found');
|
||||||
|
}
|
||||||
|
const config = await ConfigModel.findOne({
|
||||||
|
where: { key, uid: user.id },
|
||||||
|
});
|
||||||
|
if (!config) {
|
||||||
|
throw new CustomError(404, 'config not found');
|
||||||
|
}
|
||||||
|
const configData = config?.data?.permission;
|
||||||
|
if (configData?.share !== 'public') {
|
||||||
|
throw new CustomError(403, 'no permission');
|
||||||
|
}
|
||||||
|
await redis.set(`config:share:${username}:${key}`, JSON.stringify(config), 'EX', 60 * 60 * 24 * 7); // 7天
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
static async expireShareConfig(key: string, username: string) {
|
||||||
|
if (key && username) {
|
||||||
|
await redis.del(`config:share:${username}:${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/routes/config/share-config.ts
Normal file
27
src/routes/config/share-config.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { app } from '@/app.ts';
|
||||||
|
import { ShareConfigService } from './services/share.ts';
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'config',
|
||||||
|
key: 'shareConfig',
|
||||||
|
middleware: ['auth'],
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const { key, username } = ctx.query?.data || {};
|
||||||
|
if (!key) {
|
||||||
|
ctx.throw(400, 'key is required');
|
||||||
|
}
|
||||||
|
if (!username) {
|
||||||
|
ctx.throw(400, 'username is required');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const config = await ShareConfigService.getShareConfig(key, username);
|
||||||
|
ctx.body = config;
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.code === 500) {
|
||||||
|
console.error('config get error', error);
|
||||||
|
}
|
||||||
|
ctx.throw(404, 'config not found');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(app);
|
@ -1,2 +1 @@
|
|||||||
import './list.ts';
|
import './list.ts';
|
||||||
import './upload-list.ts'
|
|
@ -1,81 +1,8 @@
|
|||||||
import { app } from '@/app.ts';
|
import { app } from '@/app.ts';
|
||||||
import { MicroAppUploadModel } from './models.ts';
|
|
||||||
import { appPathCheck, installApp } from './module/install-app.ts';
|
import { appPathCheck, installApp } from './module/install-app.ts';
|
||||||
import { manager } from './manager-app.ts';
|
import { manager } from './manager-app.ts';
|
||||||
import { selfRestart } from '@/modules/self-restart.ts';
|
import { selfRestart } from '@/modules/self-restart.ts';
|
||||||
|
import { AppListModel } from '../app-manager/module/index.ts';
|
||||||
// 应用上传到 应用管理 的平台
|
|
||||||
app
|
|
||||||
.route({
|
|
||||||
path: 'micro-app',
|
|
||||||
key: 'upload',
|
|
||||||
middleware: ['auth'],
|
|
||||||
description: 'Upload micro app in server',
|
|
||||||
isDebug: true,
|
|
||||||
})
|
|
||||||
.define(async (ctx) => {
|
|
||||||
const { files, collection } = ctx.query?.data;
|
|
||||||
const { uid, username } = ctx.state.tokenUser;
|
|
||||||
const file = files[0];
|
|
||||||
console.log('File', files);
|
|
||||||
const { path, name, hash, size } = file;
|
|
||||||
const tags = [];
|
|
||||||
if (collection?.tags) {
|
|
||||||
tags.push(...collection.tags);
|
|
||||||
}
|
|
||||||
if (!name) {
|
|
||||||
ctx.throw(400, 'Invalid file');
|
|
||||||
}
|
|
||||||
let microApp = await MicroAppUploadModel.findOne({
|
|
||||||
where: { title: name, uid },
|
|
||||||
});
|
|
||||||
if (microApp) {
|
|
||||||
await MicroAppUploadModel.update(
|
|
||||||
{
|
|
||||||
type: 'micro-app',
|
|
||||||
tags,
|
|
||||||
data: {
|
|
||||||
...microApp.data,
|
|
||||||
file: {
|
|
||||||
path,
|
|
||||||
size,
|
|
||||||
name,
|
|
||||||
hash,
|
|
||||||
},
|
|
||||||
collection,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ where: { title: name, uid } },
|
|
||||||
);
|
|
||||||
microApp = await MicroAppUploadModel.findOne({
|
|
||||||
where: { title: name, uid },
|
|
||||||
});
|
|
||||||
console.log('Update micro app', microApp.id);
|
|
||||||
} else {
|
|
||||||
microApp = await MicroAppUploadModel.create({
|
|
||||||
title: name,
|
|
||||||
description: collection?.readme || '',
|
|
||||||
type: 'micro-app',
|
|
||||||
tags: tags,
|
|
||||||
data: {
|
|
||||||
file: {
|
|
||||||
path,
|
|
||||||
size,
|
|
||||||
name,
|
|
||||||
hash,
|
|
||||||
},
|
|
||||||
collection,
|
|
||||||
},
|
|
||||||
uid,
|
|
||||||
share: false,
|
|
||||||
uname: username,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.body = microApp;
|
|
||||||
})
|
|
||||||
.addTo(app);
|
|
||||||
|
|
||||||
// curl http://localhost:4002/api/router?path=micro-app&key=deploy
|
// curl http://localhost:4002/api/router?path=micro-app&key=deploy
|
||||||
// 把对应的应用安装到系统的apps目录下,并解压,然后把配置项写入数据库配置
|
// 把对应的应用安装到系统的apps目录下,并解压,然后把配置项写入数据库配置
|
||||||
// key 是应用的唯一标识,和package.json中的key一致,绑定关系
|
// key 是应用的唯一标识,和package.json中的key一致,绑定关系
|
||||||
@ -86,24 +13,25 @@ app
|
|||||||
path: 'micro-app',
|
path: 'micro-app',
|
||||||
key: 'deploy',
|
key: 'deploy',
|
||||||
description: 'Deploy micro app in server',
|
description: 'Deploy micro app in server',
|
||||||
|
middleware: ['auth'],
|
||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const { id, key, force, install } = ctx.query?.data;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
// const id = '10f03411-85fc-4d37-a4d3-e32b15566a6c';
|
const data = ctx.query?.data;
|
||||||
// const key = 'envision-cli';
|
const { id, key, force, install } = data;
|
||||||
// const id = '7c54a6de-9171-4093-926d-67a035042c6c';
|
|
||||||
// const key = 'mark';
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
ctx.throw(400, 'Invalid id');
|
ctx.throw(400, 'Invalid id');
|
||||||
}
|
}
|
||||||
const microApp = await MicroAppUploadModel.findByPk(id);
|
let username = tokenUser.username;
|
||||||
const { file } = microApp.data || {};
|
if (data.username) {
|
||||||
const path = file?.path;
|
// username = data.username;
|
||||||
if (!path) {
|
|
||||||
ctx.throw(404, 'Invalid path');
|
|
||||||
}
|
}
|
||||||
console.log('path', path);
|
const microApp = await AppListModel.findByPk(id);
|
||||||
const check = await appPathCheck({ key });
|
if (!microApp) {
|
||||||
|
ctx.throw(400, 'Invalid id');
|
||||||
|
}
|
||||||
|
const { key: appKey, version } = microApp;
|
||||||
|
const check = await appPathCheck({ key: key });
|
||||||
let appType: string;
|
let appType: string;
|
||||||
if (check) {
|
if (check) {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
@ -115,7 +43,7 @@ app
|
|||||||
await manager.removeApp(key);
|
await manager.removeApp(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const installAppData = await installApp({ path, key, needInstallDeps: !!install });
|
const installAppData = await installApp({ username, appKey, version, key, needInstallDeps: !!install });
|
||||||
await manager.add(installAppData.showAppInfo);
|
await manager.add(installAppData.showAppInfo);
|
||||||
ctx.body = installAppData;
|
ctx.body = installAppData;
|
||||||
if (appType === 'system-app') {
|
if (appType === 'system-app') {
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
import { sequelize } from '@/modules/sequelize.ts';
|
|
||||||
import { DataTypes, Model } from 'sequelize';
|
|
||||||
|
|
||||||
export type MicroApp = Partial<InstanceType<typeof MicroAppUploadModel>>;
|
|
||||||
|
|
||||||
type MicroAppData = {
|
|
||||||
file?: {
|
|
||||||
path: string;
|
|
||||||
size: number;
|
|
||||||
hash: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
key?: string;
|
|
||||||
data?: any;
|
|
||||||
collection?: any; // 上传的信息汇总
|
|
||||||
};
|
|
||||||
export class MicroAppUploadModel extends Model {
|
|
||||||
declare id: string;
|
|
||||||
declare title: string;
|
|
||||||
declare description: string;
|
|
||||||
declare type: string;
|
|
||||||
declare tags: string[];
|
|
||||||
declare data: MicroAppData;
|
|
||||||
declare uid: string;
|
|
||||||
declare updatedAt: Date;
|
|
||||||
declare createdAt: Date;
|
|
||||||
declare source: string;
|
|
||||||
declare share: boolean;
|
|
||||||
declare uname: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
MicroAppUploadModel.init(
|
|
||||||
{
|
|
||||||
id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
defaultValue: DataTypes.UUIDV4,
|
|
||||||
comment: 'id',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
type: DataTypes.JSONB,
|
|
||||||
defaultValue: [],
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
source: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
type: DataTypes.JSONB,
|
|
||||||
defaultValue: {},
|
|
||||||
},
|
|
||||||
share: {
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
defaultValue: false,
|
|
||||||
},
|
|
||||||
uname: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
uid: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
tableName: 'micro_apps_upload',
|
|
||||||
// paranoid: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
MicroAppUploadModel.sync({ alter: true, logging: false }).catch((e) => {
|
|
||||||
console.error('MicroAppUploadModel sync', e);
|
|
||||||
});
|
|
@ -1,16 +1,21 @@
|
|||||||
import { minioClient } from '@/app.ts';
|
import { minioClient } from '@/app.ts';
|
||||||
import { bucketName } from '@/modules/minio.ts';
|
import { bucketName } from '@/modules/minio.ts';
|
||||||
import { fileIsExist } from '@kevisual/use-config';
|
import { fileIsExist } from '@kevisual/use-config';
|
||||||
|
import { spawn, spawnSync } from 'child_process';
|
||||||
|
import { getFileStat, getMinioList, MinioFile } from '@/routes/file/index.ts';
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as tar from 'tar';
|
|
||||||
import { appsPath } from '../lib/index.ts';
|
import { appsPath } from '../lib/index.ts';
|
||||||
import { installAppFromKey } from './manager.ts';
|
import { installAppFromKey } from './manager.ts';
|
||||||
export type InstallAppOpts = {
|
export type InstallAppOpts = {
|
||||||
path?: string;
|
path?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
needInstallDeps?: boolean;
|
needInstallDeps?: boolean;
|
||||||
|
// minio中
|
||||||
|
appKey?: string;
|
||||||
|
version?: string;
|
||||||
|
username?: string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 检测路径是否存在
|
* 检测路径是否存在
|
||||||
@ -26,28 +31,37 @@ export const appPathCheck = async (opts: InstallAppOpts) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
export const installApp = async (opts: InstallAppOpts) => {
|
export const installApp = async (opts: InstallAppOpts) => {
|
||||||
const { key, needInstallDeps } = opts;
|
const { key, needInstallDeps, appKey, version, username } = opts;
|
||||||
const fileStream = await minioClient.getObject(bucketName, opts.path);
|
const prefix = `${username}/${appKey}/${version}`;
|
||||||
const pathName = opts.path.split('/').pop();
|
const pkgPrefix = prefix + '/package.json';
|
||||||
|
const stat = await getFileStat(pkgPrefix);
|
||||||
|
if (!stat) {
|
||||||
|
throw new Error('App not found');
|
||||||
|
}
|
||||||
|
const fileList = await getMinioList({
|
||||||
|
prefix,
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
for (const file of fileList) {
|
||||||
|
const { name } = file as MinioFile;
|
||||||
|
const outputPath = path.join(appsPath, key, name.replace(prefix, ''));
|
||||||
|
const dir = path.dirname(outputPath);
|
||||||
|
if (!fileIsExist(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
const fileStream = await minioClient.getObject(bucketName, `${name}`);
|
||||||
|
const writeStream = fs.createWriteStream(outputPath);
|
||||||
|
fileStream.pipe(writeStream);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
writeStream.on('finish', () => resolve(true));
|
||||||
|
writeStream.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
const directory = path.join(appsPath, key);
|
const directory = path.join(appsPath, key);
|
||||||
if (!fileIsExist(directory)) {
|
if (!fileIsExist(directory)) {
|
||||||
fs.mkdirSync(directory, { recursive: true });
|
fs.mkdirSync(directory, { recursive: true });
|
||||||
}
|
}
|
||||||
const filePath = path.join(directory, pathName);
|
|
||||||
|
|
||||||
const writeStream = fs.createWriteStream(filePath);
|
|
||||||
fileStream.pipe(writeStream);
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
writeStream.on('finish', () => resolve(true));
|
|
||||||
writeStream.on('error', reject);
|
|
||||||
});
|
|
||||||
// 解压 tgz文件
|
|
||||||
const extractPath = path.join(directory);
|
|
||||||
await tar.x({
|
|
||||||
file: filePath,
|
|
||||||
cwd: extractPath,
|
|
||||||
});
|
|
||||||
if (needInstallDeps) {
|
if (needInstallDeps) {
|
||||||
try {
|
try {
|
||||||
installDeps({ appPath: directory, isProduction: true, sync: true });
|
installDeps({ appPath: directory, isProduction: true, sync: true });
|
||||||
@ -57,7 +71,6 @@ export const installApp = async (opts: InstallAppOpts) => {
|
|||||||
}
|
}
|
||||||
return installAppFromKey(key);
|
return installAppFromKey(key);
|
||||||
};
|
};
|
||||||
import { spawn, spawnSync } from 'child_process';
|
|
||||||
|
|
||||||
export const checkPnpm = () => {
|
export const checkPnpm = () => {
|
||||||
try {
|
try {
|
||||||
@ -76,13 +89,16 @@ type InstallDepsOptions = {
|
|||||||
export const installDeps = async (opts: InstallDepsOptions) => {
|
export const installDeps = async (opts: InstallDepsOptions) => {
|
||||||
const { appPath } = opts;
|
const { appPath } = opts;
|
||||||
const isProduction = opts.isProduction ?? true;
|
const isProduction = opts.isProduction ?? true;
|
||||||
|
const isPnpm = checkPnpm();
|
||||||
const params = ['i'];
|
const params = ['i'];
|
||||||
if (isProduction) {
|
if (isProduction && isPnpm) {
|
||||||
params.push('--production');
|
params.push('--production');
|
||||||
|
} else {
|
||||||
|
params.push('--omit=dev');
|
||||||
}
|
}
|
||||||
console.log('installDeps', appPath, params);
|
console.log('installDeps', appPath, params);
|
||||||
const syncSpawn = opts.sync ? spawnSync : spawn;
|
const syncSpawn = opts.sync ? spawnSync : spawn;
|
||||||
if (checkPnpm()) {
|
if (isPnpm) {
|
||||||
syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: process.env });
|
syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: process.env });
|
||||||
} else {
|
} else {
|
||||||
syncSpawn('npm', params, { cwd: appPath, stdio: 'inherit', env: process.env });
|
syncSpawn('npm', params, { cwd: appPath, stdio: 'inherit', env: process.env });
|
||||||
|
@ -10,13 +10,13 @@ export const installAppFromKey = async (key: string) => {
|
|||||||
}
|
}
|
||||||
const pkgs = path.join(directory, 'package.json');
|
const pkgs = path.join(directory, 'package.json');
|
||||||
if (!fileIsExist(pkgs)) {
|
if (!fileIsExist(pkgs)) {
|
||||||
throw new Error('Invalid package.json');
|
throw new Error('Invalid package.json, must has pkg property');
|
||||||
}
|
}
|
||||||
const json = fs.readFileSync(pkgs, 'utf-8');
|
const json = fs.readFileSync(pkgs, 'utf-8');
|
||||||
const pkg = JSON.parse(json);
|
const pkg = JSON.parse(json);
|
||||||
const { name, version, app } = pkg;
|
const { name, version, app } = pkg;
|
||||||
if (!name || !version || !app) {
|
if (!name || !version || !app) {
|
||||||
throw new Error('Invalid package.json');
|
throw new Error('Invalid package.json, must has name, version, app property');
|
||||||
}
|
}
|
||||||
const readmeFile = path.join(directory, 'README.md');
|
const readmeFile = path.join(directory, 'README.md');
|
||||||
let readmeDesc = '';
|
let readmeDesc = '';
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import { App } from '@kevisual/router';
|
|
||||||
import { Manager } from '../module/manager.ts';
|
|
||||||
import { loadFileAppInfo } from '../module/load-app.ts';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const app = new App();
|
|
||||||
|
|
||||||
app
|
|
||||||
.route({
|
|
||||||
path: 'test',
|
|
||||||
key: 'test',
|
|
||||||
})
|
|
||||||
.define(async (ctx) => {
|
|
||||||
ctx.body = 'test';
|
|
||||||
});
|
|
||||||
|
|
||||||
const manager = new Manager({ mainApp: app });
|
|
||||||
|
|
||||||
await manager.load();
|
|
||||||
|
|
||||||
const { mainEntry, app: appConfig } = await loadFileAppInfo('mark');
|
|
||||||
|
|
||||||
// const appInfo = {
|
|
||||||
// key: 'mark',
|
|
||||||
// type: 'system-app',
|
|
||||||
// entry: mainEntry,
|
|
||||||
// status: 'running',
|
|
||||||
// path: path.dirname(mainEntry),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// await manager.add(appInfo);
|
|
||||||
|
|
||||||
const client = await loadFileAppInfo('micro-client');
|
|
||||||
const appInfoMicro = {
|
|
||||||
key: 'micro-client',
|
|
||||||
type: 'micro-app',
|
|
||||||
entry: client.mainEntry,
|
|
||||||
status: 'running',
|
|
||||||
path: path.dirname(client.mainEntry),
|
|
||||||
};
|
|
||||||
|
|
||||||
await manager.add(appInfoMicro);
|
|
||||||
|
|
||||||
app.listen(3005);
|
|
@ -1,8 +0,0 @@
|
|||||||
import { getAppPathKeys } from '../module/install-app.ts';
|
|
||||||
|
|
||||||
const main = () => {
|
|
||||||
const keys = getAppPathKeys();
|
|
||||||
console.log('Keys', keys);
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
@ -1,62 +0,0 @@
|
|||||||
import { app } from '@/app.ts';
|
|
||||||
import { MicroAppUploadModel } from './models.ts';
|
|
||||||
|
|
||||||
// 获取MicroAppUpload的uploadList的接口
|
|
||||||
app
|
|
||||||
.route({
|
|
||||||
path: 'micro-app-upload',
|
|
||||||
key: 'list',
|
|
||||||
middleware: ['auth'],
|
|
||||||
description: 'Get micro app upload list',
|
|
||||||
})
|
|
||||||
.define(async (ctx) => {
|
|
||||||
const { uid } = ctx.state.tokenUser;
|
|
||||||
const uploadList = await MicroAppUploadModel.findAll({
|
|
||||||
// where: { uid },
|
|
||||||
});
|
|
||||||
ctx.body = uploadList;
|
|
||||||
})
|
|
||||||
.addTo(app);
|
|
||||||
|
|
||||||
// 获取单个MicroAppUpload的接口
|
|
||||||
app
|
|
||||||
.route({
|
|
||||||
path: 'micro-app-upload',
|
|
||||||
key: 'get',
|
|
||||||
middleware: ['auth'],
|
|
||||||
description: 'Get a single micro app upload',
|
|
||||||
})
|
|
||||||
.define(async (ctx) => {
|
|
||||||
const { id, title } = ctx.query;
|
|
||||||
let upload: MicroAppUploadModel | null = null;
|
|
||||||
if (id) {
|
|
||||||
upload = await MicroAppUploadModel.findByPk(id);
|
|
||||||
} else if (title) {
|
|
||||||
upload = await MicroAppUploadModel.findOne({
|
|
||||||
where: { title },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (upload) {
|
|
||||||
ctx.body = upload;
|
|
||||||
} else {
|
|
||||||
ctx.throw(404, 'Not found');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.addTo(app);
|
|
||||||
|
|
||||||
// 删除MicroAppUpload的接口
|
|
||||||
app
|
|
||||||
.route({
|
|
||||||
path: 'micro-app-upload',
|
|
||||||
key: 'delete',
|
|
||||||
middleware: ['auth'],
|
|
||||||
description: 'Delete a micro app upload',
|
|
||||||
})
|
|
||||||
.define(async (ctx) => {
|
|
||||||
const { id } = ctx.query;
|
|
||||||
const deleted = await MicroAppUploadModel.destroy({
|
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
ctx.body = { deleted };
|
|
||||||
})
|
|
||||||
.addTo(app);
|
|
@ -12,12 +12,14 @@ export const createCookie = (token: any, ctx: any) => {
|
|||||||
if (!domain) {
|
if (!domain) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ctx.res.cookie('token', token.token, {
|
if (ctx.res.cookie) {
|
||||||
maxAge: token.expireTime,
|
ctx.res.cookie('token', token.token, {
|
||||||
domain,
|
maxAge: 7 * 24 * 60 * 60 * 1000, // 过期时间, 设置7天
|
||||||
sameSite: 'lax',
|
domain,
|
||||||
httpOnly: true,
|
sameSite: 'lax',
|
||||||
});
|
httpOnly: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const clearCookie = (ctx: any) => {
|
const clearCookie = (ctx: any) => {
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
@ -53,8 +55,11 @@ app
|
|||||||
.route({
|
.route({
|
||||||
path: 'user',
|
path: 'user',
|
||||||
key: 'login',
|
key: 'login',
|
||||||
|
middleware: ['auth-can'],
|
||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
|
const oldToken = ctx.query.token;
|
||||||
|
const tokenUser = ctx.state?.tokenUser || {};
|
||||||
const { username, email, password, loginType = 'default' } = ctx.query;
|
const { username, email, password, loginType = 'default' } = ctx.query;
|
||||||
if (!username && !email) {
|
if (!username && !email) {
|
||||||
ctx.throw(400, 'username or email is required');
|
ctx.throw(400, 'username or email is required');
|
||||||
@ -69,6 +74,15 @@ app
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
ctx.throw(500, 'Login Failed');
|
ctx.throw(500, 'Login Failed');
|
||||||
}
|
}
|
||||||
|
if (tokenUser.id === user.id) {
|
||||||
|
// 自己刷新自己的token
|
||||||
|
const token = await User.oauth.resetToken(oldToken, {
|
||||||
|
...tokenUser.oauthExpand,
|
||||||
|
});
|
||||||
|
createCookie(token, ctx);
|
||||||
|
ctx.body = token;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!user.checkPassword(password)) {
|
if (!user.checkPassword(password)) {
|
||||||
ctx.throw(500, 'Password error');
|
ctx.throw(500, 'Password error');
|
||||||
}
|
}
|
||||||
@ -89,11 +103,18 @@ app
|
|||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
app
|
app
|
||||||
.route('user', 'auth')
|
.route({
|
||||||
|
path: 'user',
|
||||||
|
key: 'auth',
|
||||||
|
middleware: ['auth-can'],
|
||||||
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const { checkToken: token } = ctx.query;
|
const { checkToken: token } = ctx.query;
|
||||||
try {
|
try {
|
||||||
const result = await User.verifyToken(token);
|
const result = await User.verifyToken(token);
|
||||||
|
if (result) {
|
||||||
|
delete result.oauthExpand;
|
||||||
|
}
|
||||||
ctx.body = result || {};
|
ctx.body = result || {};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.throw(401, 'Token InValid ');
|
ctx.throw(401, 'Token InValid ');
|
||||||
@ -102,7 +123,9 @@ app
|
|||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
|
||||||
app
|
app
|
||||||
.route('user', 'updateSelf', {
|
.route({
|
||||||
|
path: 'user',
|
||||||
|
key: 'updateSelf',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
@ -133,6 +156,60 @@ app
|
|||||||
ctx.body = await user.getInfo();
|
ctx.body = await user.getInfo();
|
||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'user',
|
||||||
|
key: 'switchCheck',
|
||||||
|
middleware: ['auth'],
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const token = ctx.query.token;
|
||||||
|
const { username, accessToken } = ctx.query.data || {};
|
||||||
|
|
||||||
|
if (accessToken && username) {
|
||||||
|
const accessUser = await User.verifyToken(accessToken);
|
||||||
|
const refreshToken = accessUser.oauthExpand?.refreshToken;
|
||||||
|
if (refreshToken) {
|
||||||
|
const result = await User.oauth.refreshToken(refreshToken);
|
||||||
|
createCookie(result, ctx);
|
||||||
|
ctx.body = result;
|
||||||
|
return;
|
||||||
|
} else if (accessUser) {
|
||||||
|
await User.oauth.delToken(accessToken);
|
||||||
|
const result = await User.oauth.generateToken(accessUser, {
|
||||||
|
...accessUser.oauthExpand,
|
||||||
|
hasRefreshToken: true,
|
||||||
|
});
|
||||||
|
createCookie(result, ctx);
|
||||||
|
ctx.body = result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result = await ctx.call(
|
||||||
|
{
|
||||||
|
path: 'user',
|
||||||
|
key: 'switchOrg',
|
||||||
|
payload: {
|
||||||
|
data: {
|
||||||
|
username,
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
res: ctx.res,
|
||||||
|
req: ctx.req,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (result.code === 200) {
|
||||||
|
ctx.body = result.body;
|
||||||
|
} else {
|
||||||
|
ctx.throw(result.code, result.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
app
|
app
|
||||||
.route({
|
.route({
|
||||||
path: 'user',
|
path: 'user',
|
||||||
@ -141,63 +218,60 @@ app
|
|||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
const { username, type = 'org', loginType } = ctx.query.data || {};
|
const token = ctx.query.token;
|
||||||
if (!username && type === 'org') {
|
const tokenUsername = tokenUser.username;
|
||||||
ctx.throw('username is required');
|
const userId = tokenUser.userId;
|
||||||
|
let { username } = ctx.query.data || {};
|
||||||
|
const user = await User.findByPk(userId);
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw('user not found');
|
||||||
}
|
}
|
||||||
if (tokenUser.username === username) {
|
if (!username) {
|
||||||
// 自己刷新自己的token
|
username = user.username;
|
||||||
const user = await User.findByPk(tokenUser.id);
|
|
||||||
if (!user) {
|
|
||||||
ctx.throw('user not found');
|
|
||||||
}
|
|
||||||
if (user.type === 'user') {
|
|
||||||
const token = await user.createToken(null, loginType);
|
|
||||||
createCookie(token, ctx);
|
|
||||||
ctx.body = token;
|
|
||||||
return;
|
|
||||||
} else if (user.type === 'org' && tokenUser.uid) {
|
|
||||||
const token = await user.createToken(tokenUser.uid, loginType);
|
|
||||||
createCookie(token, ctx);
|
|
||||||
ctx.body = token;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let me: User;
|
|
||||||
if (tokenUser.uid) {
|
const orgs = await user.getOrgs();
|
||||||
me = await User.findByPk(tokenUser.uid);
|
const orgsList = [tokenUser.username, user.username, , ...orgs];
|
||||||
|
if (orgsList.includes(username)) {
|
||||||
|
if (tokenUsername === username) {
|
||||||
|
const result = await User.oauth.resetToken(token);
|
||||||
|
createCookie(result, ctx);
|
||||||
|
await User.oauth.delToken(token);
|
||||||
|
ctx.body = result;
|
||||||
|
} else {
|
||||||
|
const user = await User.findOne({ where: { username } });
|
||||||
|
const result = await user.createToken(userId, 'default');
|
||||||
|
createCookie(result, ctx);
|
||||||
|
ctx.body = result;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
me = await User.findByPk(tokenUser.id); // 真实用户
|
ctx.throw(403, 'Permission denied');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'user',
|
||||||
|
key: 'refreshToken',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const { refreshToken } = ctx.query.data || {};
|
||||||
|
try {
|
||||||
|
if (!refreshToken) {
|
||||||
|
ctx.throw(400, 'Refresh Token is required');
|
||||||
|
}
|
||||||
|
const result = await User.oauth.refreshToken(refreshToken);
|
||||||
|
if (result) {
|
||||||
|
console.log('refreshToken result', result);
|
||||||
|
createCookie(result, ctx);
|
||||||
|
ctx.body = result;
|
||||||
|
} else {
|
||||||
|
ctx.throw(500, 'Refresh Token Failed, please login again');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('refreshToken error', e);
|
||||||
|
ctx.throw(400, 'Refresh Token Failed');
|
||||||
}
|
}
|
||||||
if (!me || me.type === 'org') {
|
|
||||||
console.log('switch Error ', me.username, me.type);
|
|
||||||
ctx.throw('Permission denied');
|
|
||||||
}
|
|
||||||
if (type === 'user') {
|
|
||||||
const token = await me.createToken(null, loginType);
|
|
||||||
createCookie(token, ctx);
|
|
||||||
ctx.body = token;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const orgUser = await User.findOne({ where: { username } });
|
|
||||||
if (!orgUser) {
|
|
||||||
ctx.throw('org user not found');
|
|
||||||
}
|
|
||||||
if (orgUser.type === 'user') {
|
|
||||||
// 想转换的type===org, 但实际上这个用户名是一个用户, 比如org调用switchOrg root
|
|
||||||
const token = await orgUser.createToken(null, loginType);
|
|
||||||
createCookie(token, ctx);
|
|
||||||
ctx.body = token;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const user = await Org.findOne({ where: { username } });
|
|
||||||
const users = user.users;
|
|
||||||
const index = users.findIndex((u) => u.uid === me.id);
|
|
||||||
if (index === -1) {
|
|
||||||
ctx.throw('Permission denied');
|
|
||||||
}
|
|
||||||
const token = await orgUser.createToken(me.id, loginType);
|
|
||||||
createCookie(token, ctx);
|
|
||||||
ctx.body = token;
|
|
||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
@ -52,6 +52,8 @@ app
|
|||||||
await org.addUser(user, { needPermission: true, role: role || 'user', operateId, isAdmin: !!tokenAdmin });
|
await org.addUser(user, { needPermission: true, role: role || 'user', operateId, isAdmin: !!tokenAdmin });
|
||||||
} else if (action === 'remove') {
|
} else if (action === 'remove') {
|
||||||
await org.removeUser(user, { needPermission: true, operateId, isAdmin: !!tokenAdmin });
|
await org.removeUser(user, { needPermission: true, operateId, isAdmin: !!tokenAdmin });
|
||||||
|
} else if (action === 'update') {
|
||||||
|
await org.addUser(user, { needPermission: true, role: role || 'user', operateId, isAdmin: !!tokenAdmin });
|
||||||
} else {
|
} else {
|
||||||
ctx.throw('操作错误');
|
ctx.throw('操作错误');
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,7 @@ app
|
|||||||
path: 'org',
|
path: 'org',
|
||||||
key: 'hasUser',
|
key: 'hasUser',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
|
description: '判断当前username这个组织,是否在当前用户的组织中。如果有,返回当前组织的用户信息,否则返回null',
|
||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
@ -159,7 +160,13 @@ app
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const usernameUser = await User.findOne({ where: { username } });
|
const usernameUser = await User.findOne({
|
||||||
|
where: { username },
|
||||||
|
attributes: {
|
||||||
|
exclude: ['password', 'salt'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!usernameUser) {
|
if (!usernameUser) {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
uid: null,
|
uid: null,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user