import { useConfig } from '@kevisual/use-config'; import { sequelize } from '@/modules/sequelize.ts'; import { DataTypes, Model, Op } 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 { redis } from '@/app.ts'; 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); } 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.findAndCountAll(); console.info('[User count]', w.count); if (w.count < 1) { 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 w = await User.findAndCountAll({ logging: false, }); console.info('[User count]', w.count); 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); 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(); 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; }