关于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",
|
||||
"release": "node ./config/release/index.mjs",
|
||||
"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": [],
|
||||
"types": "types/index.d.ts",
|
||||
@ -32,7 +33,6 @@
|
||||
],
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@kevisual/auth": "1.0.5",
|
||||
"@kevisual/local-app-manager": "0.1.9",
|
||||
"@kevisual/router": "0.0.9",
|
||||
"@kevisual/use-config": "^1.0.9",
|
||||
@ -46,10 +46,10 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minio": "^8.0.5",
|
||||
"nanoid": "^5.1.3",
|
||||
"nanoid": "^5.1.5",
|
||||
"node-fetch": "^3.3.2",
|
||||
"p-queue": "^8.1.0",
|
||||
"pg": "^8.14.0",
|
||||
"pg": "^8.14.1",
|
||||
"pm2": "^6.0.5",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"semver": "^7.7.1",
|
||||
@ -61,7 +61,7 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/code-center-module": "0.0.13",
|
||||
"@kevisual/code-center-module": "workspace:*",
|
||||
"@kevisual/types": "^0.0.6",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
@ -75,15 +75,15 @@
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"nodemon": "^3.1.9",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.35.0",
|
||||
"rollup": "^4.36.0",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-dts": "^6.2.0",
|
||||
"tape": "^5.9.0",
|
||||
"tsx": "^4.19.3",
|
||||
"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 { app } from './app.ts';
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
import { User } from './models/user.ts';
|
||||
import { createAuthRoute } from '@kevisual/auth';
|
||||
const config = useConfig<{ tokenSecret: string }>();
|
||||
import { addAuth } from '@kevisual/code-center-module/models';
|
||||
|
||||
createAuthRoute({
|
||||
app,
|
||||
secret: config.tokenSecret,
|
||||
});
|
||||
addAuth(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
|
@ -7,8 +7,6 @@ import { useFileStore } from '@kevisual/use-config/file-store';
|
||||
import { app, minioClient } from '@/app.ts';
|
||||
import { bucketName } from '@/modules/minio.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 });
|
||||
|
||||
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 {
|
||||
try {
|
||||
return JSON.parse(collection);
|
||||
|
@ -15,8 +15,9 @@ export const validateDirectory = (directory?: string) => {
|
||||
};
|
||||
}
|
||||
// 把directory的/替换掉后,只能包含数字、字母、下划线、中划线
|
||||
// 可以包含.
|
||||
let _directory = directory?.replace(/\//g, '');
|
||||
if (_directory && !/^[a-zA-Z0-9_-]+$/.test(_directory)) {
|
||||
if (_directory && !/^[a-zA-Z0-9_.-]+$/.test(_directory)) {
|
||||
return {
|
||||
code: 500,
|
||||
message: 'directory is invalid, only number, letter, underline and hyphen are allowed',
|
||||
|
@ -176,7 +176,7 @@ app
|
||||
uid,
|
||||
version: version || '0.0.0',
|
||||
title: appKey,
|
||||
proxy: true,
|
||||
proxy: appKey.includes('center') ? false : true,
|
||||
data: {
|
||||
files: files || [],
|
||||
},
|
||||
@ -352,7 +352,7 @@ app
|
||||
user: checkUsername,
|
||||
uid,
|
||||
data: { files: needAddFiles },
|
||||
proxy: true,
|
||||
proxy: appKey.includes('center') ? false : true,
|
||||
});
|
||||
} else {
|
||||
const appModel = await AppModel.findOne({ where: { key: appKey, version, uid } });
|
||||
|
@ -18,8 +18,9 @@ export interface AppData {
|
||||
password?: string; // 受保护的访问密码
|
||||
'expiration-time'?: string; // 受保护的访问过期时间
|
||||
};
|
||||
// 运行环境,browser, node, 或者其他,是数组
|
||||
runtime?: string[];
|
||||
}
|
||||
export type AppType = 'web-single' | 'web-module'; // 可以做到网页代理
|
||||
export enum AppStatus {
|
||||
running = 'running',
|
||||
stop = 'stop',
|
||||
@ -36,9 +37,7 @@ export class AppModel extends Model {
|
||||
declare description: string;
|
||||
declare version: string;
|
||||
declare domain: string;
|
||||
declare appType: string;
|
||||
declare key: string;
|
||||
declare type: string;
|
||||
declare uid: string;
|
||||
declare pid: string;
|
||||
// 是否是history路由代理模式。静态的直接转minio,而不需要缓存下来。
|
||||
@ -74,18 +73,10 @@ AppModel.init(
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: '',
|
||||
},
|
||||
appType: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: '',
|
||||
},
|
||||
key: {
|
||||
type: DataTypes.STRING,
|
||||
// 和 uid 组合唯一
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: '',
|
||||
},
|
||||
uid: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
|
@ -2,6 +2,7 @@ import { app } from '@/app.ts';
|
||||
import { AppModel } from '../module/index.ts';
|
||||
|
||||
// curl http://localhost:4005/api/router?path=app&key=public-list
|
||||
// TODO:
|
||||
app
|
||||
.route({
|
||||
path: 'app',
|
||||
@ -12,6 +13,9 @@ app
|
||||
where: {
|
||||
status: 'running',
|
||||
},
|
||||
// attributes: {
|
||||
// exclude: ['data'],
|
||||
// },
|
||||
logging: false,
|
||||
});
|
||||
ctx.body = list;
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { CustomError } from '@kevisual/router';
|
||||
import { AppModel, AppListModel } from './module/index.ts';
|
||||
import { app } from '@/app.ts';
|
||||
import { setExpire } from './revoke.ts';
|
||||
import { getMinioListAndSetToAppList } from '../file/index.ts';
|
||||
import { getUidByUsername } from './util.ts';
|
||||
|
||||
app
|
||||
.route({
|
||||
@ -18,6 +15,9 @@ app
|
||||
where: {
|
||||
uid: tokenUser.id,
|
||||
},
|
||||
attributes: {
|
||||
exclude: ['data'],
|
||||
},
|
||||
});
|
||||
ctx.body = list;
|
||||
return ctx;
|
||||
@ -29,24 +29,25 @@ app
|
||||
path: 'user-app',
|
||||
key: 'get',
|
||||
middleware: ['auth'],
|
||||
description: '获取用户应用,可以指定id或者key',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const id = ctx.query.id;
|
||||
const { key } = ctx.query.data || {};
|
||||
if (!id && !key) {
|
||||
throw new CustomError('id is required');
|
||||
ctx.throw(500, 'id is required');
|
||||
}
|
||||
if (id) {
|
||||
const am = await AppModel.findByPk(id);
|
||||
if (!am) {
|
||||
throw new CustomError('app not found');
|
||||
ctx.throw(500, 'app not found');
|
||||
}
|
||||
ctx.body = am;
|
||||
} else {
|
||||
const am = await AppModel.findOne({ where: { key, uid: tokenUser.id } });
|
||||
if (!am) {
|
||||
throw new CustomError('app not found');
|
||||
ctx.throw(500, 'app not found');
|
||||
}
|
||||
ctx.body = am;
|
||||
}
|
||||
@ -75,16 +76,16 @@ app
|
||||
setExpire(newApp.key, app.user);
|
||||
}
|
||||
} else {
|
||||
throw new CustomError('app not found');
|
||||
ctx.throw(500, 'app not found');
|
||||
}
|
||||
return;
|
||||
}
|
||||
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 } });
|
||||
if (findApp) {
|
||||
throw new CustomError('key already exists');
|
||||
ctx.throw(500, 'key already exists');
|
||||
}
|
||||
const app = await AppModel.create({
|
||||
data: { files: [] },
|
||||
@ -107,14 +108,14 @@ app
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const id = ctx.query.id;
|
||||
if (!id) {
|
||||
throw new CustomError('id is required');
|
||||
ctx.throw(500, 'id is required');
|
||||
}
|
||||
const am = await AppModel.findByPk(id);
|
||||
if (!am) {
|
||||
throw new CustomError('app not found');
|
||||
ctx.throw(500, 'app not found');
|
||||
}
|
||||
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 } });
|
||||
await am.destroy({ force: true });
|
||||
@ -133,11 +134,11 @@ app
|
||||
.define(async (ctx) => {
|
||||
const id = ctx.query.id;
|
||||
if (!id) {
|
||||
throw new CustomError('id is required');
|
||||
ctx.throw(500, 'id is required');
|
||||
}
|
||||
const am = await AppListModel.findByPk(id);
|
||||
if (!am) {
|
||||
throw new CustomError('app not found');
|
||||
ctx.throw(500, 'app not found');
|
||||
}
|
||||
const amJson = am.toJSON();
|
||||
ctx.body = {
|
||||
@ -146,5 +147,3 @@ app
|
||||
};
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
import './list.ts';
|
||||
import './upload-config.ts';
|
||||
import './share-config.ts';
|
@ -1,5 +1,6 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { ConfigModel } from './models/model.ts';
|
||||
import { ShareConfigService } from './services/share.ts';
|
||||
app
|
||||
.route({
|
||||
path: 'config',
|
||||
@ -12,6 +13,7 @@ app
|
||||
where: {
|
||||
uid: id,
|
||||
},
|
||||
order: [['updatedAt', 'DESC']],
|
||||
});
|
||||
ctx.body = {
|
||||
list: config,
|
||||
@ -19,3 +21,123 @@ 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 {
|
||||
key?: string;
|
||||
version?: string;
|
||||
permission?: {
|
||||
share?: 'public' | 'private';
|
||||
};
|
||||
}
|
||||
|
||||
export type Config = Partial<InstanceType<typeof ConfigModel>>;
|
||||
@ -70,7 +73,6 @@ export class ConfigModel extends Model {
|
||||
static async getUploadConfig(opts: { uid: string }) {
|
||||
const defaultConfig = {
|
||||
key: 'upload',
|
||||
type: 'upload',
|
||||
version: '1.0.0',
|
||||
};
|
||||
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 './upload-list.ts'
|
@ -1,81 +1,8 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { MicroAppUploadModel } from './models.ts';
|
||||
import { appPathCheck, installApp } from './module/install-app.ts';
|
||||
import { manager } from './manager-app.ts';
|
||||
import { selfRestart } from '@/modules/self-restart.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);
|
||||
|
||||
import { AppListModel } from '../app-manager/module/index.ts';
|
||||
// curl http://localhost:4002/api/router?path=micro-app&key=deploy
|
||||
// 把对应的应用安装到系统的apps目录下,并解压,然后把配置项写入数据库配置
|
||||
// key 是应用的唯一标识,和package.json中的key一致,绑定关系
|
||||
@ -86,24 +13,25 @@ app
|
||||
path: 'micro-app',
|
||||
key: 'deploy',
|
||||
description: 'Deploy micro app in server',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { id, key, force, install } = ctx.query?.data;
|
||||
// const id = '10f03411-85fc-4d37-a4d3-e32b15566a6c';
|
||||
// const key = 'envision-cli';
|
||||
// const id = '7c54a6de-9171-4093-926d-67a035042c6c';
|
||||
// const key = 'mark';
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const data = ctx.query?.data;
|
||||
const { id, key, force, install } = data;
|
||||
if (!id) {
|
||||
ctx.throw(400, 'Invalid id');
|
||||
}
|
||||
const microApp = await MicroAppUploadModel.findByPk(id);
|
||||
const { file } = microApp.data || {};
|
||||
const path = file?.path;
|
||||
if (!path) {
|
||||
ctx.throw(404, 'Invalid path');
|
||||
let username = tokenUser.username;
|
||||
if (data.username) {
|
||||
// username = data.username;
|
||||
}
|
||||
console.log('path', path);
|
||||
const check = await appPathCheck({ key });
|
||||
const microApp = await AppListModel.findByPk(id);
|
||||
if (!microApp) {
|
||||
ctx.throw(400, 'Invalid id');
|
||||
}
|
||||
const { key: appKey, version } = microApp;
|
||||
const check = await appPathCheck({ key: key });
|
||||
let appType: string;
|
||||
if (check) {
|
||||
if (!force) {
|
||||
@ -115,7 +43,7 @@ app
|
||||
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);
|
||||
ctx.body = installAppData;
|
||||
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 { bucketName } from '@/modules/minio.ts';
|
||||
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 path from 'path';
|
||||
import * as tar from 'tar';
|
||||
import { appsPath } from '../lib/index.ts';
|
||||
import { installAppFromKey } from './manager.ts';
|
||||
export type InstallAppOpts = {
|
||||
path?: string;
|
||||
key?: string;
|
||||
needInstallDeps?: boolean;
|
||||
// minio中
|
||||
appKey?: string;
|
||||
version?: string;
|
||||
username?: string;
|
||||
};
|
||||
/**
|
||||
* 检测路径是否存在
|
||||
@ -26,28 +31,37 @@ export const appPathCheck = async (opts: InstallAppOpts) => {
|
||||
return false;
|
||||
};
|
||||
export const installApp = async (opts: InstallAppOpts) => {
|
||||
const { key, needInstallDeps } = opts;
|
||||
const fileStream = await minioClient.getObject(bucketName, opts.path);
|
||||
const pathName = opts.path.split('/').pop();
|
||||
const directory = path.join(appsPath, key);
|
||||
if (!fileIsExist(directory)) {
|
||||
fs.mkdirSync(directory, { recursive: true });
|
||||
const { key, needInstallDeps, appKey, version, username } = opts;
|
||||
const prefix = `${username}/${appKey}/${version}`;
|
||||
const pkgPrefix = prefix + '/package.json';
|
||||
const stat = await getFileStat(pkgPrefix);
|
||||
if (!stat) {
|
||||
throw new Error('App not found');
|
||||
}
|
||||
const filePath = path.join(directory, pathName);
|
||||
|
||||
const writeStream = fs.createWriteStream(filePath);
|
||||
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);
|
||||
});
|
||||
// 解压 tgz文件
|
||||
const extractPath = path.join(directory);
|
||||
await tar.x({
|
||||
file: filePath,
|
||||
cwd: extractPath,
|
||||
});
|
||||
}
|
||||
const directory = path.join(appsPath, key);
|
||||
if (!fileIsExist(directory)) {
|
||||
fs.mkdirSync(directory, { recursive: true });
|
||||
}
|
||||
if (needInstallDeps) {
|
||||
try {
|
||||
installDeps({ appPath: directory, isProduction: true, sync: true });
|
||||
@ -57,7 +71,6 @@ export const installApp = async (opts: InstallAppOpts) => {
|
||||
}
|
||||
return installAppFromKey(key);
|
||||
};
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
|
||||
export const checkPnpm = () => {
|
||||
try {
|
||||
@ -76,13 +89,16 @@ type InstallDepsOptions = {
|
||||
export const installDeps = async (opts: InstallDepsOptions) => {
|
||||
const { appPath } = opts;
|
||||
const isProduction = opts.isProduction ?? true;
|
||||
const isPnpm = checkPnpm();
|
||||
const params = ['i'];
|
||||
if (isProduction) {
|
||||
if (isProduction && isPnpm) {
|
||||
params.push('--production');
|
||||
} else {
|
||||
params.push('--omit=dev');
|
||||
}
|
||||
console.log('installDeps', appPath, params);
|
||||
const syncSpawn = opts.sync ? spawnSync : spawn;
|
||||
if (checkPnpm()) {
|
||||
if (isPnpm) {
|
||||
syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: process.env });
|
||||
} else {
|
||||
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');
|
||||
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 pkg = JSON.parse(json);
|
||||
const { name, version, app } = pkg;
|
||||
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');
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
if (ctx.res.cookie) {
|
||||
ctx.res.cookie('token', token.token, {
|
||||
maxAge: token.expireTime,
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 过期时间, 设置7天
|
||||
domain,
|
||||
sameSite: 'lax',
|
||||
httpOnly: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
const clearCookie = (ctx: any) => {
|
||||
if (!domain) {
|
||||
@ -53,8 +55,11 @@ app
|
||||
.route({
|
||||
path: 'user',
|
||||
key: 'login',
|
||||
middleware: ['auth-can'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const oldToken = ctx.query.token;
|
||||
const tokenUser = ctx.state?.tokenUser || {};
|
||||
const { username, email, password, loginType = 'default' } = ctx.query;
|
||||
if (!username && !email) {
|
||||
ctx.throw(400, 'username or email is required');
|
||||
@ -69,6 +74,15 @@ app
|
||||
if (!user) {
|
||||
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)) {
|
||||
ctx.throw(500, 'Password error');
|
||||
}
|
||||
@ -89,11 +103,18 @@ app
|
||||
})
|
||||
.addTo(app);
|
||||
app
|
||||
.route('user', 'auth')
|
||||
.route({
|
||||
path: 'user',
|
||||
key: 'auth',
|
||||
middleware: ['auth-can'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const { checkToken: token } = ctx.query;
|
||||
try {
|
||||
const result = await User.verifyToken(token);
|
||||
if (result) {
|
||||
delete result.oauthExpand;
|
||||
}
|
||||
ctx.body = result || {};
|
||||
} catch (e) {
|
||||
ctx.throw(401, 'Token InValid ');
|
||||
@ -102,7 +123,9 @@ app
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route('user', 'updateSelf', {
|
||||
.route({
|
||||
path: 'user',
|
||||
key: 'updateSelf',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
@ -133,6 +156,60 @@ app
|
||||
ctx.body = await user.getInfo();
|
||||
})
|
||||
.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
|
||||
.route({
|
||||
path: 'user',
|
||||
@ -141,63 +218,60 @@ app
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const { username, type = 'org', loginType } = ctx.query.data || {};
|
||||
if (!username && type === 'org') {
|
||||
ctx.throw('username is required');
|
||||
}
|
||||
if (tokenUser.username === username) {
|
||||
// 自己刷新自己的token
|
||||
const user = await User.findByPk(tokenUser.id);
|
||||
const token = ctx.query.token;
|
||||
const tokenUsername = tokenUser.username;
|
||||
const userId = tokenUser.userId;
|
||||
let { username } = ctx.query.data || {};
|
||||
const user = await User.findByPk(userId);
|
||||
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;
|
||||
if (!username) {
|
||||
username = user.username;
|
||||
}
|
||||
}
|
||||
let me: User;
|
||||
if (tokenUser.uid) {
|
||||
me = await User.findByPk(tokenUser.uid);
|
||||
|
||||
const orgs = await user.getOrgs();
|
||||
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 {
|
||||
me = await User.findByPk(tokenUser.id); // 真实用户
|
||||
const user = await User.findOne({ where: { username } });
|
||||
const result = await user.createToken(userId, 'default');
|
||||
createCookie(result, ctx);
|
||||
ctx.body = result;
|
||||
}
|
||||
if (!me || me.type === 'org') {
|
||||
console.log('switch Error ', me.username, me.type);
|
||||
ctx.throw('Permission denied');
|
||||
} else {
|
||||
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 (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);
|
||||
|
@ -52,6 +52,8 @@ app
|
||||
await org.addUser(user, { needPermission: true, role: role || 'user', operateId, isAdmin: !!tokenAdmin });
|
||||
} else if (action === 'remove') {
|
||||
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 {
|
||||
ctx.throw('操作错误');
|
||||
}
|
||||
|
@ -143,6 +143,7 @@ app
|
||||
path: 'org',
|
||||
key: 'hasUser',
|
||||
middleware: ['auth'],
|
||||
description: '判断当前username这个组织,是否在当前用户的组织中。如果有,返回当前组织的用户信息,否则返回null',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
@ -159,7 +160,13 @@ app
|
||||
};
|
||||
return;
|
||||
}
|
||||
const usernameUser = await User.findOne({ where: { username } });
|
||||
const usernameUser = await User.findOne({
|
||||
where: { username },
|
||||
attributes: {
|
||||
exclude: ['password', 'salt'],
|
||||
},
|
||||
});
|
||||
|
||||
if (!usernameUser) {
|
||||
ctx.body = {
|
||||
uid: null,
|
||||
|
Loading…
x
Reference in New Issue
Block a user