- Replaced Sequelize models with Drizzle ORM for app and app list management. - Updated routes in app-manager to utilize new database queries. - Removed obsolete Sequelize model files for app, app list, and app domain. - Introduced new helper functions for app and app domain management. - Enhanced user app management with improved file handling and user migration. - Adjusted public API routes to align with new database structure. - Implemented caching mechanisms for domain management.
188 lines
5.3 KiB
TypeScript
188 lines
5.3 KiB
TypeScript
import { app, db, schema } from '@/app.ts';
|
|
import { User } from '@/models/user.ts';
|
|
import { nanoid } from 'nanoid';
|
|
import { CustomError } from '@kevisual/router';
|
|
import { backupUserA, deleteUser, mvUserAToUserB } from '@/routes/file/index.ts';
|
|
import { AppHelper } from '@/routes/app-manager/module/index.ts';
|
|
import { eq } from 'drizzle-orm';
|
|
// import { mvAppFromUserAToUserB } from '@/routes/app-manager/admin/mv-user-app.ts';
|
|
|
|
export const checkUsername = (username: string) => {
|
|
if (username.length > 30) {
|
|
throw new CustomError(400, '用户名不能过长');
|
|
}
|
|
if (!/^[a-zA-Z0-9_@]+$/.test(username)) {
|
|
throw new CustomError(400, '用户名包含非法字符');
|
|
}
|
|
if (username.includes(' ')) {
|
|
throw new CustomError(400, '用户名不能包含空格');
|
|
}
|
|
};
|
|
export const checkUsernameShort = (username: string) => {
|
|
if (username.length <= 3) {
|
|
throw new CustomError(400, '用户名不能过短');
|
|
}
|
|
};
|
|
|
|
export const toChangeName = async (opts: { id: number; newName: string; admin?: boolean, ctx: any }) => {
|
|
const { id, newName, admin = false, ctx } = opts;
|
|
if (!admin) {
|
|
checkUsernameShort(newName);
|
|
}
|
|
const user = await User.findByPk(id);
|
|
if (!user) {
|
|
ctx.throw(404, 'User not found');
|
|
}
|
|
const oldName = user.username;
|
|
checkUsername(newName);
|
|
const findUserByUsername = await User.findOne({ where: { username: newName } });
|
|
if (findUserByUsername) {
|
|
ctx.throw(400, 'Username already exists');
|
|
}
|
|
user.username = newName;
|
|
const data = user.data || {};
|
|
data.canChangeUsername = false;
|
|
user.data = data;
|
|
try {
|
|
await user.save();
|
|
// 迁移文件数据
|
|
await backupUserA(oldName, user.id); // 备份文件数据
|
|
await mvUserAToUserB(oldName, newName, true); // 迁移文件数据
|
|
// await mvAppFromUserAToUserB(oldName, newName); // 迁移应用数据
|
|
|
|
if (['org', 'user'].includes(user.type)) {
|
|
const type = user.type === 'org' ? 'org' : 'user';
|
|
await User.clearUserToken(user.id, type); // 清除旧token
|
|
}
|
|
// 更新应用数据中的用户名
|
|
const apps = await db.select().from(schema.kvApp).where(eq(schema.kvApp.user, oldName));
|
|
for (const appItem of apps) {
|
|
const appData = appItem.data as any;
|
|
const newFiles = await AppHelper.getNewFiles(appData.files || [], { oldUser: oldName, newUser: newName });
|
|
await db.update(schema.kvApp)
|
|
.set({ user: newName, data: { ...appData, files: newFiles }, updatedAt: new Date().toISOString() })
|
|
.where(eq(schema.kvApp.id, appItem.id));
|
|
}
|
|
} catch (error) {
|
|
console.error('迁移文件数据失败', error);
|
|
ctx.throw(500, 'Failed to change username');
|
|
}
|
|
return user;
|
|
}
|
|
app
|
|
.route({
|
|
path: 'user',
|
|
key: 'changeName',
|
|
middleware: ['auth-admin'],
|
|
})
|
|
.define(async (ctx) => {
|
|
const { id, newName } = ctx.query.data || {};
|
|
try {
|
|
if (!id || !newName) {
|
|
ctx.throw(400, '参数错误');
|
|
}
|
|
const user = await toChangeName({ id, newName, admin: true, ctx });
|
|
ctx.body = await user?.getInfo?.();
|
|
} catch (error) {
|
|
console.error('changeName error', error);
|
|
ctx.throw(500, 'Failed to change username');
|
|
}
|
|
})
|
|
.addTo(app);
|
|
|
|
app
|
|
.route({
|
|
path: 'user',
|
|
key: 'checkUserExist',
|
|
middleware: ['auth'],
|
|
})
|
|
.define(async (ctx) => {
|
|
const { username } = ctx.query.data || {};
|
|
if (!username) {
|
|
ctx.throw(400, 'Username is required');
|
|
}
|
|
checkUsername(username);
|
|
const user = await User.findOne({ username });
|
|
|
|
ctx.body = {
|
|
id: user?.id,
|
|
username: user?.username,
|
|
};
|
|
})
|
|
.addTo(app);
|
|
|
|
app
|
|
.route({
|
|
path: 'user',
|
|
key: 'resetPassword',
|
|
middleware: ['auth-admin'],
|
|
})
|
|
.define(async (ctx) => {
|
|
const { id, password } = ctx.query.data || {};
|
|
const user = await User.findByPk(id);
|
|
if (!user) {
|
|
ctx.throw(404, 'User not found');
|
|
}
|
|
let pwd = password || nanoid(6);
|
|
user.createPassword(pwd);
|
|
await user.save();
|
|
|
|
ctx.body = {
|
|
id: user.id,
|
|
username: user.username,
|
|
password: !password ? pwd : undefined,
|
|
};
|
|
})
|
|
.addTo(app);
|
|
|
|
app
|
|
.route({
|
|
path: 'user',
|
|
key: 'createNewUser',
|
|
middleware: ['auth-admin'],
|
|
})
|
|
.define(async (ctx) => {
|
|
const { username, password, description } = ctx.query.data || {};
|
|
if (!username) {
|
|
ctx.throw(400, 'Username is required');
|
|
}
|
|
checkUsername(username);
|
|
const findUserByUsername = await User.findOne({ username });
|
|
if (findUserByUsername) {
|
|
ctx.throw(400, 'Username already exists');
|
|
}
|
|
let pwd = password || nanoid(6);
|
|
const user = await User.createUser(username, pwd, description);
|
|
ctx.body = {
|
|
id: user.id,
|
|
username: user.username,
|
|
description: user.description,
|
|
password: pwd,
|
|
};
|
|
})
|
|
.addTo(app);
|
|
|
|
app
|
|
.route({
|
|
path: 'user',
|
|
key: 'deleteUser',
|
|
middleware: ['auth-admin'],
|
|
})
|
|
.define(async (ctx) => {
|
|
const { id } = ctx.query.data || {};
|
|
const user = await User.findByPk(id);
|
|
if (!user) {
|
|
ctx.throw(404, 'User not found');
|
|
}
|
|
await db.delete(schema.cfUser).where(eq(schema.cfUser.id, user.id));
|
|
backupUserA(user.username, user.id);
|
|
deleteUser(user.username);
|
|
// TODO: EXPIRE 删除token
|
|
ctx.body = {
|
|
id: user.id,
|
|
username: user.username,
|
|
message: 'User deleted successfully',
|
|
};
|
|
})
|
|
.addTo(app);
|