185 lines
5.0 KiB
TypeScript
185 lines
5.0 KiB
TypeScript
import { DataTypes, Model, Op, Sequelize } from 'sequelize';
|
||
import { useContextKey } from '@kevisual/context';
|
||
import { SyncOpts, User } from './user.ts';
|
||
|
||
type AddUserOpts = {
|
||
role: string;
|
||
};
|
||
export enum OrgRole {
|
||
admin = 'admin',
|
||
member = 'member',
|
||
owner = 'owner',
|
||
}
|
||
export class Org extends Model {
|
||
declare id: string;
|
||
declare username: string;
|
||
declare description: string;
|
||
declare users: { role: string; uid: string }[];
|
||
/**
|
||
* operateId 是真实操作者的id
|
||
* @param user
|
||
* @param opts
|
||
*/
|
||
async addUser(user: User, opts?: { operateId?: string; role: string; needPermission?: boolean; isAdmin?: boolean }) {
|
||
const hasUser = this.users.find((u) => u.uid === user.id);
|
||
if (hasUser) {
|
||
return;
|
||
}
|
||
if (user.type !== 'user') {
|
||
throw Error('Only user can be added to org');
|
||
}
|
||
if (opts?.needPermission) {
|
||
if (opts?.isAdmin) {
|
||
} else {
|
||
const adminUsers = this.users.filter((u) => u.role === 'admin' || u.role === 'owner');
|
||
const adminIds = adminUsers.map((u) => u.uid);
|
||
const hasPermission = adminIds.includes(opts.operateId);
|
||
if (!hasPermission) {
|
||
throw Error('No permission');
|
||
}
|
||
}
|
||
}
|
||
try {
|
||
await user.expireOrgs();
|
||
} catch (e) {
|
||
console.error('expireOrgs', e);
|
||
}
|
||
const users = [...this.users];
|
||
if (opts?.role === 'owner') {
|
||
const orgOwner = users.find((u) => u.role === 'owner');
|
||
if (opts.isAdmin) {
|
||
} else {
|
||
if (!opts.operateId) {
|
||
throw Error('operateId is required');
|
||
}
|
||
const owner = await User.findByPk(opts?.operateId);
|
||
if (!owner) {
|
||
throw Error('operateId is not found');
|
||
}
|
||
if (orgOwner?.uid !== owner.id) {
|
||
throw Error('No permission');
|
||
}
|
||
}
|
||
if (orgOwner) {
|
||
orgOwner.role = 'admin';
|
||
}
|
||
users.push({ role: 'owner', uid: user.id });
|
||
} else {
|
||
users.push({ role: opts?.role || 'member', uid: user.id });
|
||
}
|
||
await Org.update({ users }, { where: { id: this.id } });
|
||
|
||
}
|
||
/**
|
||
* operateId 是真实操作者的id
|
||
* @param user
|
||
* @param opts
|
||
*/
|
||
async removeUser(user: User, opts?: { operateId?: string; needPermission?: boolean; isAdmin?: boolean }) {
|
||
if (opts?.needPermission) {
|
||
if (opts?.isAdmin) {
|
||
} else {
|
||
const adminUsers = this.users.filter((u) => u.role === 'admin' || u.role === 'owner');
|
||
const adminIds = adminUsers.map((u) => u.uid);
|
||
const hasPermission = adminIds.includes(opts.operateId);
|
||
if (!hasPermission) {
|
||
throw Error('No permission');
|
||
}
|
||
}
|
||
}
|
||
await user.expireOrgs();
|
||
const users = this.users.filter((u) => u.uid !== user.id || u.role === 'owner');
|
||
await Org.update({ users }, { where: { id: this.id } });
|
||
}
|
||
/**
|
||
* operateId 是真实操作者的id
|
||
* @param user
|
||
* @param opts
|
||
*/
|
||
async getUsers(opts?: { operateId: string; needPermission?: boolean; isAdmin?: boolean }) {
|
||
const usersIds = this.users.map((u) => u.uid);
|
||
const orgUser = this.users;
|
||
if (opts?.needPermission) {
|
||
// 不在组织内或者不是管理员,如果需要权限,返回空
|
||
if (opts.isAdmin) {
|
||
} else {
|
||
const hasPermission = usersIds.includes(opts.operateId);
|
||
if (!hasPermission) {
|
||
return {
|
||
hasPermission: false,
|
||
users: [],
|
||
};
|
||
}
|
||
}
|
||
}
|
||
const _users = await User.findAll({
|
||
where: {
|
||
id: {
|
||
[Op.in]: usersIds,
|
||
},
|
||
},
|
||
});
|
||
|
||
const users = _users.map((u) => {
|
||
const role = orgUser.find((r) => r.uid === u.id)?.role;
|
||
return {
|
||
id: u.id,
|
||
username: u.username,
|
||
role: role,
|
||
};
|
||
});
|
||
return { users };
|
||
}
|
||
/**
|
||
* 检测用户是否在组织内,且角色为role
|
||
* @param user
|
||
* @param opts
|
||
*/
|
||
async getInRole(userId: string, role = 'admin') {
|
||
const user = this.users.find((u) => u.uid === userId && u.role === role);
|
||
return !!user;
|
||
}
|
||
}
|
||
/**
|
||
* 组织模型,在sequelize之后初始化
|
||
*/
|
||
export const OrgInit = async (newSequelize?: any, tableName?: string, sync?: SyncOpts) => {
|
||
const sequelize = useContextKey<Sequelize>('sequelize');
|
||
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: newSequelize || sequelize,
|
||
modelName: tableName || 'cf_org',
|
||
paranoid: true,
|
||
},
|
||
);
|
||
if (sync) {
|
||
await Org.sync({ alter: true, logging: false, ...sync }).catch((e) => {
|
||
console.error('Org sync', e);
|
||
});
|
||
return Org;
|
||
}
|
||
return Org;
|
||
};
|
||
export const OrgModel = useContextKey('OrgModel', () => Org);
|