init
This commit is contained in:
13
src/app.ts
Normal file
13
src/app.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { App } from '@kevisual/router';
|
||||
import { useContextKey, useContext } from '@kevisual/use-config/context';
|
||||
|
||||
const init = () => {
|
||||
return new App({
|
||||
serverOptions: {
|
||||
cors: {
|
||||
origin: '*',
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
export const app = useContextKey('app', init);
|
||||
0
src/dev.ts
Normal file
0
src/dev.ts
Normal file
14
src/index.ts
Normal file
14
src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { app } from './app.ts';
|
||||
import { UserServices } from './models/user.ts';
|
||||
import { Org } from './models/org.ts';
|
||||
import { useContextKey } from '@kevisual/use-config/context';
|
||||
import { Sequelize } from 'sequelize';
|
||||
import { Redis } from 'ioredis';
|
||||
export const User = UserServices;
|
||||
export { Org };
|
||||
|
||||
export const redis = useContextKey<Redis>('redis');
|
||||
export const sequelize = useContextKey<Sequelize>('sequelize');
|
||||
export const UserModel = useContextKey<typeof UserServices>('UserModel');
|
||||
export const OrgModel = useContextKey<typeof Org>('OrgModel');
|
||||
export { app };
|
||||
44
src/models/org.ts
Normal file
44
src/models/org.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
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.sync({ alter: true, logging: false }).catch((e) => {
|
||||
console.error('Org sync', e);
|
||||
});
|
||||
|
||||
useContextKey('OrgModel', () => Org);
|
||||
287
src/models/user.ts
Normal file
287
src/models/user.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
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 }>();
|
||||
|
||||
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',
|
||||
};
|
||||
}
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
|
||||
useContextKey('UserModel', () => UserServices);
|
||||
Reference in New Issue
Block a user