feat: update org operate

This commit is contained in:
熊潇 2025-02-28 13:35:13 +08:00
parent 409067f13f
commit d2280f6b89
14 changed files with 542 additions and 400 deletions

View File

@ -16,7 +16,8 @@
"reload": "ssh light pm2 restart codecenter",
"pub": "npm run build && npm run deploy && npm run reload",
"start": "pm2 start dist/app.mjs --name codecenter",
"release": "node ./config/release/index.mjs"
"release": "node ./config/release/index.mjs",
"ssl": "ssl -L 6379:localhost:6379 light "
},
"keywords": [],
"types": "types/index.d.ts",
@ -45,6 +46,7 @@
"node-fetch": "^3.3.2",
"p-queue": "^8.1.0",
"pg": "^8.13.3",
"pm2": "^5.4.3",
"rollup-plugin-esbuild": "^6.2.1",
"semver": "^7.7.1",
"sequelize": "^6.37.5",
@ -52,10 +54,10 @@
"strip-ansi": "^7.1.0",
"tar": "^7.4.3",
"uuid": "^11.1.0",
"pm2": "^5.4.3",
"zod": "^3.24.2"
},
"devDependencies": {
"@kevisual/code-center-module": "^0.0.9",
"@kevisual/types": "^0.0.6",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^28.0.2",

29
pnpm-lock.yaml generated
View File

@ -95,6 +95,9 @@ importers:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@kevisual/code-center-module':
specifier: ^0.0.9
version: 0.0.9(@kevisual/auth@1.0.5)(@kevisual/router@0.0.7)(@kevisual/use-config@1.0.8)(ioredis@5.5.0)(pg@8.13.3)(sequelize@6.37.5(pg@8.13.3))
'@kevisual/types':
specifier: ^0.0.6
version: 0.0.6
@ -348,6 +351,16 @@ packages:
'@kevisual/auth@1.0.5':
resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==}
'@kevisual/code-center-module@0.0.9':
resolution: {integrity: sha512-ErHTYUlJGXmeBV5RJezKbBRmTYVBABV0SVvcMvbWVWPNc13Zo5RvM29vnzY6w3b6/cpnpWBi7Vx8WGFrcsMOkw==}
peerDependencies:
'@kevisual/auth': ^1.0.5
'@kevisual/router': ^0.0.7
'@kevisual/use-config': ^1.0.8
ioredis: ^5.5.0
pg: ^8.13.3
sequelize: ^6.37.5
'@kevisual/local-app-manager@0.1.9':
resolution: {integrity: sha512-oppYUjvpPg/7gBw1h3dX63fhxB8MvGc74yGgIM4JifvSuOYLcZ9oZyauYV+5lQVh2tjJImOuUmRHnVXu1FWKDQ==}
peerDependencies:
@ -2562,6 +2575,22 @@ snapshots:
'@kevisual/auth@1.0.5': {}
'@kevisual/code-center-module@0.0.9(@kevisual/auth@1.0.5)(@kevisual/router@0.0.7)(@kevisual/use-config@1.0.8)(ioredis@5.5.0)(pg@8.13.3)(sequelize@6.37.5(pg@8.13.3))':
dependencies:
'@kevisual/auth': 1.0.5
'@kevisual/router': 0.0.7
'@kevisual/use-config': 1.0.8
ioredis: 5.5.0
nanoid: 5.1.2
pg: 8.13.3
sequelize: 6.37.5(pg@8.13.3)
socket.io: 4.8.1
zod: 3.24.2
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
'@kevisual/local-app-manager@0.1.9(@kevisual/router@0.0.7)(@kevisual/types@0.0.6)(@kevisual/use-config@1.0.8)(pm2@5.4.3)':
dependencies:
'@kevisual/router': 0.0.7

View File

@ -1,44 +1,48 @@
import { DataTypes, Model, Sequelize } from 'sequelize';
import { useContextKey } from '@kevisual/use-config/context';
const sequelize = useContextKey<Sequelize>('sequelize');
export class Org extends Model {
declare id: string;
declare username: string;
declare description: string;
declare users: { role: string; uid: string }[];
}
// import { DataTypes, Model, Sequelize } from 'sequelize';
// import { useContextKey } from '@kevisual/use-config/context';
// const sequelize = useContextKey<Sequelize>('sequelize');
// export class Org extends Model {
// declare id: string;
// declare username: string;
// declare description: string;
// declare users: { role: string; uid: string }[];
// }
Org.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
description: {
type: DataTypes.STRING,
allowNull: true,
},
users: {
type: DataTypes.JSONB,
allowNull: true,
defaultValue: [],
},
},
{
sequelize,
modelName: 'cf_org',
paranoid: true,
},
);
// Org.init(
// {
// id: {
// type: DataTypes.UUID,
// primaryKey: true,
// defaultValue: DataTypes.UUIDV4,
// },
// username: {
// type: DataTypes.STRING,
// allowNull: false,
// unique: true,
// },
// description: {
// type: DataTypes.STRING,
// allowNull: true,
// },
// users: {
// type: DataTypes.JSONB,
// allowNull: true,
// defaultValue: [],
// },
// },
// {
// sequelize,
// modelName: 'cf_org',
// paranoid: true,
// },
// );
Org.sync({ alter: true, logging: false }).catch((e) => {
console.error('Org sync', e);
});
// Org.sync({ alter: true, logging: false }).catch((e) => {
// console.error('Org sync', e);
// });
useContextKey('OrgModel', () => Org);
// useContextKey('OrgModel', () => Org);
import { Org, OrgInit } from '@kevisual/code-center-module/models';
export { Org };
OrgInit();

View File

@ -1,287 +1,293 @@
import { useConfig } from '@kevisual/use-config';
import { DataTypes, Model, Op, Sequelize } from 'sequelize';
import { createToken, checkToken } from '@kevisual/auth';
import { cryptPwd } from '@kevisual/auth';
import { customRandom, nanoid, customAlphabet } from 'nanoid';
import { CustomError } from '@kevisual/router';
import { Org } from './org.ts';
// import { useConfig } from '@kevisual/use-config';
// import { DataTypes, Model, Op, Sequelize } from 'sequelize';
// import { createToken, checkToken } from '@kevisual/auth';
// import { cryptPwd } from '@kevisual/auth';
// import { customRandom, nanoid, customAlphabet } from 'nanoid';
// import { CustomError } from '@kevisual/router';
// import { Org } from './org.ts';
import { useContextKey } from '@kevisual/use-config/context';
import { Redis } from 'ioredis';
export const redis = useContextKey<Redis>('redis');
const sequelize = useContextKey<Sequelize>('sequelize');
const config = useConfig<{ tokenSecret: string }>();
// import { useContextKey } from '@kevisual/use-config/context';
// import { Redis } from 'ioredis';
// export const redis = useContextKey<Redis>('redis');
// const sequelize = useContextKey<Sequelize>('sequelize');
// const config = useConfig<{ tokenSecret: string }>();
type UserData = {
orgs?: string[];
};
export class User extends Model {
declare id: string;
declare username: string;
declare nickname: string; // 昵称
declare alias: string; // 别名
declare password: string;
declare salt: string;
declare needChangePassword: boolean;
declare description: string;
declare data: UserData;
declare type: string; // user | org | visitor
declare owner: string;
declare orgId: string;
declare email: string;
declare avatar: string;
tokenUser: any;
setTokenUser(tokenUser: any) {
this.tokenUser = tokenUser;
}
/**
* uid orgId id id
* @param uid
* @returns
*/
async createToken(uid?: string, loginType?: 'default' | 'plugin' | 'month' | 'season' | 'year') {
const { id, username, type } = this;
let expireTime = 60 * 60 * 24 * 7; // 7 days
switch (loginType) {
case 'plugin':
expireTime = 60 * 60 * 24 * 30 * 12; // 365 days
break;
case 'month':
expireTime = 60 * 60 * 24 * 30; // 30 days
break;
case 'season':
expireTime = 60 * 60 * 24 * 30 * 3; // 90 days
break;
case 'year':
expireTime = 60 * 60 * 24 * 30 * 12; // 365 days
break;
}
const now = new Date().getTime();
const token = await createToken({ id, username, uid, type }, config.tokenSecret);
return { token, expireTime: now + expireTime };
}
static async verifyToken(token: string) {
const ct = await checkToken(token, config.tokenSecret);
const tokenUser = ct.payload;
return tokenUser;
}
static async createUser(username: string, password?: string, description?: string) {
const user = await User.findOne({ where: { username } });
if (user) {
throw new CustomError('User already exists');
}
const salt = nanoid(6);
let needChangePassword = !password;
password = password || '123456';
const cPassword = cryptPwd(password, salt);
return await User.create({ username, password: cPassword, description, salt, needChangePassword });
}
static async createOrg(username: string, owner: string, description?: string) {
const user = await User.findOne({ where: { username } });
if (user) {
throw new CustomError('User already exists');
}
const me = await User.findByPk(owner);
if (!me) {
throw new CustomError('Owner not found');
}
if (me.type !== 'user') {
throw new CustomError('Owner type is not user');
}
const org = await Org.create({ username, description, users: [{ uid: owner, role: 'owner' }] });
const newUser = await User.create({ username, password: '', description, type: 'org', owner, orgId: org.id });
// owner add
await redis.del(`user:${me.id}:orgs`);
return newUser;
}
createPassword(password: string) {
const salt = this.salt;
const cPassword = cryptPwd(password, salt);
this.password = cPassword;
return cPassword;
}
checkPassword(password: string) {
const salt = this.salt;
const cPassword = cryptPwd(password, salt);
return this.password === cPassword;
}
async getInfo() {
const orgs = await this.getOrgs();
return {
id: this.id,
username: this.username,
nickname: this.nickname,
description: this.description,
needChangePassword: this.needChangePassword,
type: this.type,
avatar: this.avatar,
orgs,
};
}
async getOrgs() {
let id = this.id;
if (this.type === 'org') {
if (this.tokenUser && this.tokenUser.uid) {
id = this.tokenUser.uid;
} else {
console.log('getOrgs', 'no uid', this.id, this.username);
throw new CustomError('Permission denied');
}
}
const cache = await redis.get(`user:${id}:orgs`);
if (cache) {
return JSON.parse(cache) as string[];
}
const orgs = await Org.findAll({
order: [['updatedAt', 'DESC']],
where: {
users: {
[Op.contains]: [
{
uid: id,
},
],
},
},
});
const orgNames = orgs.map((org) => org.username);
if (orgNames.length > 0) {
await redis.set(`user:${id}:orgs`, JSON.stringify(orgNames), 'EX', 60 * 60); // 1 hour
}
return orgNames;
}
async expireOrgs() {
await redis.del(`user:${this.id}:orgs`);
}
}
User.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
// 用户名或者手机号
// 创建后避免修改的字段,当注册用户后,用户名注册则默认不能用手机号
},
nickname: {
type: DataTypes.TEXT,
allowNull: true,
},
alias: {
type: DataTypes.TEXT,
allowNull: true, // 别名网络请求的别名需要唯一不能和username重复
},
password: {
type: DataTypes.STRING,
allowNull: true,
},
email: {
type: DataTypes.STRING,
allowNull: true,
},
avatar: {
type: DataTypes.TEXT,
allowNull: true,
},
salt: {
type: DataTypes.STRING,
allowNull: true,
},
description: {
type: DataTypes.TEXT,
},
type: {
type: DataTypes.STRING,
defaultValue: 'user',
},
owner: {
type: DataTypes.UUID,
},
orgId: {
type: DataTypes.UUID,
},
needChangePassword: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
data: {
type: DataTypes.JSON,
defaultValue: {},
},
},
{
sequelize,
tableName: 'cf_user', // codeflow user
paranoid: true,
},
);
User.sync({ alter: true, logging: false })
.then((res) => {
initializeUser();
})
.catch((err) => {
console.error('Sync User error', err);
});
const letter = 'abcdefghijklmnopqrstuvwxyz';
const custom = customAlphabet(letter, 6);
export const initializeUser = async (pwd = custom()) => {
const w = await User.findOne({ where: { username: 'root' }, logging: false });
if (!w) {
const root = await User.createUser('root', pwd, '系统管理员');
const org = await User.createOrg('admin', root.id, '管理员');
console.info(' new Users name', root.username, org.username);
console.info('new Users root password', pwd);
console.info('new Users id', root.id, org.id);
const demo = await createDemoUser();
return {
code: 200,
data: { root, org, pwd: pwd, demo },
};
} else {
return {
code: 500,
message: 'Users has been created',
};
}
};
export const createDemoUser = async (username = 'demo', pwd = custom()) => {
const u = await User.findOne({ where: { username }, logging: false });
if (!u) {
const user = await User.createUser(username, pwd, 'demo');
console.info('new Users name', user.username, pwd);
return {
code: 200,
data: { user, pwd: pwd },
};
} else {
console.info('Users has been created', u.username);
return {
code: 500,
message: 'Users has been created',
};
}
};
// type UserData = {
// orgs?: string[];
// };
// export class User extends Model {
// declare id: string;
// declare username: string;
// declare nickname: string; // 昵称
// declare alias: string; // 别名
// declare password: string;
// declare salt: string;
// declare needChangePassword: boolean;
// declare description: string;
// declare data: UserData;
// declare type: string; // user | org | visitor
// declare owner: string;
// declare orgId: string;
// declare email: string;
// declare avatar: string;
// tokenUser: any;
// setTokenUser(tokenUser: any) {
// this.tokenUser = tokenUser;
// }
// /**
// * uid 是用于 orgId 的用户id 真实用户的id
// * @param uid
// * @returns
// */
// async createToken(uid?: string, loginType?: 'default' | 'plugin' | 'month' | 'season' | 'year') {
// const { id, username, type } = this;
// let expireTime = 60 * 60 * 24 * 7; // 7 days
// switch (loginType) {
// case 'plugin':
// expireTime = 60 * 60 * 24 * 30 * 12; // 365 days
// break;
// case 'month':
// expireTime = 60 * 60 * 24 * 30; // 30 days
// break;
// case 'season':
// expireTime = 60 * 60 * 24 * 30 * 3; // 90 days
// break;
// case 'year':
// expireTime = 60 * 60 * 24 * 30 * 12; // 365 days
// break;
// }
// const now = new Date().getTime();
// const token = await createToken({ id, username, uid, type }, config.tokenSecret);
// return { token, expireTime: now + expireTime };
// }
// static async verifyToken(token: string) {
// const ct = await checkToken(token, config.tokenSecret);
// const tokenUser = ct.payload;
// return tokenUser;
// }
// static async createUser(username: string, password?: string, description?: string) {
// const user = await User.findOne({ where: { username } });
// if (user) {
// throw new CustomError('User already exists');
// }
// const salt = nanoid(6);
// let needChangePassword = !password;
// password = password || '123456';
// const cPassword = cryptPwd(password, salt);
// return await User.create({ username, password: cPassword, description, salt, needChangePassword });
// }
// static async createOrg(username: string, owner: string, description?: string) {
// const user = await User.findOne({ where: { username } });
// if (user) {
// throw new CustomError('User already exists');
// }
// const me = await User.findByPk(owner);
// if (!me) {
// throw new CustomError('Owner not found');
// }
// if (me.type !== 'user') {
// throw new CustomError('Owner type is not user');
// }
// const org = await Org.create({ username, description, users: [{ uid: owner, role: 'owner' }] });
// const newUser = await User.create({ username, password: '', description, type: 'org', owner, orgId: org.id });
// // owner add
// await redis.del(`user:${me.id}:orgs`);
// return newUser;
// }
// createPassword(password: string) {
// const salt = this.salt;
// const cPassword = cryptPwd(password, salt);
// this.password = cPassword;
// return cPassword;
// }
// checkPassword(password: string) {
// const salt = this.salt;
// const cPassword = cryptPwd(password, salt);
// return this.password === cPassword;
// }
// async getInfo() {
// const orgs = await this.getOrgs();
// return {
// id: this.id,
// username: this.username,
// nickname: this.nickname,
// description: this.description,
// needChangePassword: this.needChangePassword,
// type: this.type,
// avatar: this.avatar,
// orgs,
// };
// }
// async getOrgs() {
// let id = this.id;
// if (this.type === 'org') {
// if (this.tokenUser && this.tokenUser.uid) {
// id = this.tokenUser.uid;
// } else {
// console.log('getOrgs', 'no uid', this.id, this.username);
// throw new CustomError('Permission denied');
// }
// }
// const cache = await redis.get(`user:${id}:orgs`);
// if (cache) {
// return JSON.parse(cache) as string[];
// }
// const orgs = await Org.findAll({
// order: [['updatedAt', 'DESC']],
// where: {
// users: {
// [Op.contains]: [
// {
// uid: id,
// },
// ],
// },
// },
// });
// const orgNames = orgs.map((org) => org.username);
// if (orgNames.length > 0) {
// await redis.set(`user:${id}:orgs`, JSON.stringify(orgNames), 'EX', 60 * 60); // 1 hour
// }
// return orgNames;
// }
// async expireOrgs() {
// await redis.del(`user:${this.id}:orgs`);
// }
// }
// User.init(
// {
// id: {
// type: DataTypes.UUID,
// primaryKey: true,
// defaultValue: DataTypes.UUIDV4,
// },
// username: {
// type: DataTypes.STRING,
// allowNull: false,
// unique: true,
// // 用户名或者手机号
// // 创建后避免修改的字段,当注册用户后,用户名注册则默认不能用手机号
// },
// nickname: {
// type: DataTypes.TEXT,
// allowNull: true,
// },
// alias: {
// type: DataTypes.TEXT,
// allowNull: true, // 别名网络请求的别名需要唯一不能和username重复
// defaultValue: '',
// },
// password: {
// type: DataTypes.STRING,
// allowNull: true,
// },
// email: {
// type: DataTypes.STRING,
// allowNull: true,
// },
// avatar: {
// type: DataTypes.TEXT,
// allowNull: true,
// },
// salt: {
// type: DataTypes.STRING,
// allowNull: true,
// },
// description: {
// type: DataTypes.TEXT,
// },
// type: {
// type: DataTypes.STRING,
// defaultValue: 'user',
// },
// owner: {
// type: DataTypes.UUID,
// },
// orgId: {
// type: DataTypes.UUID,
// },
// needChangePassword: {
// type: DataTypes.BOOLEAN,
// defaultValue: false,
// },
// data: {
// type: DataTypes.JSONB,
// defaultValue: {},
// },
// },
// {
// sequelize,
// tableName: 'cf_user', // codeflow user
// paranoid: true,
// },
// );
// User.sync({ alter: true, logging: false })
// .then((res) => {
// initializeUser();
// })
// .catch((err) => {
// console.error('Sync User error', err);
// });
export class UserServices extends User {
static async loginByPhone(phone: string) {
let user = await User.findOne({ where: { username: phone } });
let isNew = false;
if (!user) {
user = await User.createUser(phone, phone.slice(-6));
isNew = true;
}
const token = await user.createToken(null, 'season');
return { ...token, isNew };
}
static initializeUser = initializeUser;
static createDemoUser = createDemoUser;
}
// const letter = 'abcdefghijklmnopqrstuvwxyz';
// const custom = customAlphabet(letter, 6);
// export const initializeUser = async (pwd = custom()) => {
// const w = await User.findOne({ where: { username: 'root' }, logging: false });
// if (!w) {
// const root = await User.createUser('root', pwd, '系统管理员');
// const org = await User.createOrg('admin', root.id, '管理员');
// console.info(' new Users name', root.username, org.username);
// console.info('new Users root password', pwd);
// console.info('new Users id', root.id, org.id);
// const demo = await createDemoUser();
// return {
// code: 200,
// data: { root, org, pwd: pwd, demo },
// };
// } else {
// return {
// code: 500,
// message: 'Users has been created',
// };
// }
// };
// export const createDemoUser = async (username = 'demo', pwd = custom()) => {
// const u = await User.findOne({ where: { username }, logging: false });
// if (!u) {
// const user = await User.createUser(username, pwd, 'demo');
// console.info('new Users name', user.username, pwd);
// return {
// code: 200,
// data: { user, pwd: pwd },
// };
// } else {
// console.info('Users has been created', u.username);
// return {
// code: 500,
// message: 'Users has been created',
// };
// }
// };
// // initializeUser();
useContextKey('UserModel', () => UserServices);
// export class UserServices extends User {
// static async loginByPhone(phone: string) {
// let user = await User.findOne({ where: { username: phone } });
// let isNew = false;
// if (!user) {
// user = await User.createUser(phone, phone.slice(-6));
// isNew = true;
// }
// const token = await user.createToken(null, 'season');
// return { ...token, isNew };
// }
// static initializeUser = initializeUser;
// static createDemoUser = createDemoUser;
// }
// useContextKey('UserModel', () => UserServices);
import { User, UserServices, UserInit } from '@kevisual/code-center-module/models';
export { User, UserServices };
UserInit();

View File

@ -46,6 +46,46 @@ app
}
})
.addTo(app);
app
.route({
path: 'auth-check',
key: 'admin',
id: 'check-auth-admin',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
if (!tokenUser) {
ctx.throw(401, 'No User For authorized');
}
try {
const user = await User.findOne({
where: {
id: tokenUser.id,
},
});
if (!user) {
ctx.throw(404, 'user not found');
}
user.setTokenUser(tokenUser);
const orgs = await user.getOrgs();
if (orgs.includes('admin')) {
ctx.body = 'admin';
ctx.state.tokenAdmin = {
id: user.id,
username: user.username,
orgs,
};
return;
}
ctx.body = 'not admin';
} catch (e) {
console.error(`auth-admin error`, e);
console.error('tokenUser', tokenUser?.id, tokenUser?.username, tokenUser?.uid);
ctx.throw(500, e.message);
}
})
.addTo(app);
app
.route({

View File

@ -60,20 +60,11 @@ add.run = async (ctx) => {
},
});
await containerModel.save();
if (containerModel.code || containerModel.source || containerModel.sourceType) {
ctx.emit?.('pageEdit', {
source: 'container',
data: containerModel.toJSON(),
operation: 'edit',
});
}
}
} else {
try {
containerModel = await ContainerModel.create({
...container,
source: '',
sourceType: '',
uid: tokenUser.id,
});
} catch (e) {
@ -127,6 +118,7 @@ app
if (!key || !version || !fileName) {
return;
}
if (container.type === 'render-js') {
const uploadResult = await uploadMinioContainer({
key,
tokenUser: ctx.state.tokenUser,
@ -147,5 +139,8 @@ app
},
},
});
} else {
ctx.throw(500, 'container type not supported:' + container.type);
}
})
.addTo(app);

View File

@ -16,16 +16,23 @@ export type Container = Partial<InstanceType<typeof ContainerModel>>;
*/
export class ContainerModel extends Model {
declare id: string;
// 标题
declare title: string;
// 描述
declare description: string;
// 类型
declare type: string;
// 标签
declare tags: string[];
// 代码
declare code: string;
// hash 值
declare hash: string;
declare source: string;
declare sourceType: string;
// 数据
declare data: ContainerData;
// 发布
declare publish: ContainerPublish;
// 用户 id
declare uid: string;
declare updatedAt: Date;
declare createdAt: Date;
@ -45,11 +52,11 @@ ContainerModel.init(
comment: 'id',
},
title: {
type: DataTypes.STRING,
type: DataTypes.TEXT,
defaultValue: '',
},
description: {
type: DataTypes.STRING,
type: DataTypes.TEXT,
defaultValue: '',
},
tags: {
@ -57,8 +64,8 @@ ContainerModel.init(
defaultValue: [],
},
type: {
type: DataTypes.STRING,
defaultValue: '',
type: DataTypes.STRING, // 代码类型, html, js, render-js
defaultValue: 'render-js',
},
code: {
type: DataTypes.TEXT,
@ -68,14 +75,6 @@ ContainerModel.init(
type: DataTypes.TEXT,
defaultValue: '',
},
source: {
type: DataTypes.STRING,
defaultValue: '',
},
sourceType: {
type: DataTypes.STRING,
defaultValue: '',
},
data: {
type: DataTypes.JSON,
defaultValue: {},

View File

@ -8,3 +8,5 @@ import './update.ts'
import './init.ts'
import './web-login.ts'
import './org-user/list.ts'

View File

@ -72,6 +72,7 @@ app
if (!user.checkPassword(password)) {
ctx.throw(500, 'Password error');
}
user.expireOrgs();
const token = await user.createToken(null, loginType);
createCookie(token, ctx);
ctx.body = token;

View File

View File

@ -0,0 +1,55 @@
import { app } from '@/app.ts';
import { User } from '@/models/user.ts';
import { Org } from '@/models/org.ts';
// 获取组织的用户列表
app
.route({
path: 'org-user',
key: 'list',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const { orgId } = ctx.query;
const org = await Org.findByPk(orgId);
if (!org) {
ctx.throw('组织不存在');
}
// const users = await user.getUsers();
ctx.body = org;
})
.addTo(app);
app
.route({
path: 'org-user',
key: 'operate',
middleware: ['check-auth-admin'],
})
.define(async (ctx) => {
const tokenAdmin = ctx.state.tokenAdmin;
const tokenUser = ctx.state.tokenUser;
const data = ctx.query.data;
const { orgId, userId, action } = data;
const org = await Org.findByPk(orgId);
if (!org) {
ctx.throw('组织不存在');
}
const user = await User.findByPk(userId);
if (!user) {
ctx.throw('用户不存在');
}
if (user.type !== 'user') {
ctx.throw('用户类型错误');
}
const operateId = tokenUser.uid || tokenUser.id;
if (action === 'add') {
await org.addUser(user, { needPermission: true, role: 'user', operateId, isAdmin: !!tokenAdmin });
} else if (action === 'remove') {
await org.removeUser(user, { needPermission: true, operateId, isAdmin: !!tokenAdmin });
} else {
ctx.throw('操作错误');
}
ctx.body = 'ok';
})
.addTo(app);

View File

@ -1,7 +1,6 @@
import { app, sequelize } from '@/app.ts';
import { Org } from '@/models/org.ts';
import { User } from '@/models/user.ts';
import { CustomError } from '@kevisual/router';
import { Op } from 'sequelize';
app
@ -35,18 +34,19 @@ app
.route({
path: 'org',
key: 'update',
middleware: ['auth'],
middleware: ['auth-admin'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
// username 为org的名字在用户表中也是唯一的
const { username, description, id } = ctx.query.data;
if (!username) {
throw new CustomError('username is required');
ctx.throw('username is required');
}
if (id) {
const org = await Org.findByPk(id);
if (!org) {
throw new CustomError('org not found');
ctx.throw('org not found');
}
org.description = description;
await org.save();
@ -62,11 +62,11 @@ app
}
const user = await User.findByPk(tokenUser.id);
if (!user) {
throw new CustomError('user not found');
ctx.throw('user not found');
}
const orgs = await user.getOrgs();
if (!orgs.includes('admin')) {
throw new CustomError('Permission denied');
ctx.throw('Permission denied');
}
const newUser = await User.createOrg(username, tokenUser.id, description);
ctx.body = {
@ -87,17 +87,17 @@ app
const tokenUser = ctx.state.tokenUser;
const id = ctx.query.id;
if (!id) {
throw new CustomError('id is required');
ctx.throw('id is required');
}
const org = await Org.findByPk(id);
if (!org) {
throw new CustomError('org not found');
ctx.throw('org not found');
}
const username = org.username;
const users = org.users;
const owner = users.find((u) => u.role === 'owner');
if (owner.uid !== tokenUser.id) {
throw new CustomError('Permission denied');
ctx.throw('Permission denied');
}
await org.destroy({ force: true });
const orgUser = await User.findOne({
@ -118,36 +118,21 @@ app
const tokenUser = ctx.state.tokenUser;
const id = ctx.query.id;
if (!id) {
throw new CustomError('id is required');
ctx.throw('id is required');
}
const org = await Org.findByPk(id);
if (!org) {
throw new CustomError('org not found');
ctx.throw('org not found');
}
const usersIds = org.users;
const me = usersIds.find((u) => u.uid === tokenUser.id);
if (!me) {
throw new CustomError('Permission denied');
ctx.throw('Permission denied');
}
const _users = await User.findAll({
where: {
id: {
[Op.in]: usersIds.map((u) => u.uid),
},
},
});
const users = _users.map((u) => {
const role = usersIds.find((r) => r.uid === u.id)?.role;
return {
id: u.id,
username: u.username,
role: role,
};
});
const orgGetUser = await org.getUsers();
ctx.body = {
org,
users,
users: orgGetUser.users,
};
})
.addTo(app);

21
src/scripts/container.ts Normal file
View File

@ -0,0 +1,21 @@
import { ContainerModel } from '../routes/container/models/index.ts';
const main = async () => {
// await ContainerModel.update(
// {
// type: 'render-js',
// },
// {
// where: {
// type: '',
// },
// },
// );
const containers = await ContainerModel.findAll();
for (const container of containers) {
console.log(container.id, container.type);
}
process.exit(0);
};
main();

View File

@ -4,6 +4,9 @@ import { User } from '../models/user.ts';
// console.log('sync user done');
// });
User.findOne({ where: { username: 'admin' } }).then((user) => {
console.log('user', user);
});
// class UserChange extends User {
// static async syncUser() {
// await UserChange.sync({ alter: true, logging: false });
// console.log('sync user done');
// }
// }