Refactor app management to use Drizzle ORM

- 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.
This commit is contained in:
2026-02-07 01:26:16 +08:00
parent d62a75842f
commit 7dfa96d165
40 changed files with 1066 additions and 1171 deletions

View File

@@ -1,10 +1,25 @@
import { DataTypes, Model, Sequelize } from 'sequelize';
import { useContextKey } from '@kevisual/context';
import { Redis } from 'ioredis';
import { SyncOpts, User } from './user.ts';
import { User } from './user.ts';
import { oauth } from '../oauth/auth.ts';
import { OauthUser } from '../oauth/oauth.ts';
import { db } from '../../modules/db.ts';
import { cfUserSecrets, cfUser } from '../../db/drizzle/schema.ts';
import { eq, InferSelectModel, InferInsertModel } from 'drizzle-orm';
const userSecretsTable = cfUserSecrets;
const usersTable = cfUser;
export type UserSecretData = {
[key: string]: any;
wxOpenid?: string;
wxUnionid?: string;
wxmpOpenid?: string;
};
export type UserSecretSelect = InferSelectModel<typeof cfUserSecrets>;
export type UserSecretInsert = InferInsertModel<typeof cfUserSecrets>;
export const redis = useContextKey<Redis>('redis');
const UserSecretStatus = ['active', 'inactive', 'expired'] as const;
@@ -16,33 +31,22 @@ const randomString = (length: number) => {
}
return result;
};
type Data = {
[key: string]: any;
/**
* 微信开放平台的某一个应用的openid
*/
wxOpenid?: string;
/**
* 微信开放平台的unionid主要
*/
wxUnionid?: string;
/**
* 微信公众号的openid次要
*/
wxmpOpenid?: string;
}
export class UserSecret extends Model {
export class UserSecret {
static oauth = oauth;
declare id: string;
declare token: string;
declare userId: string;
declare orgId: string;
declare title: string;
declare description: string;
declare status: (typeof UserSecretStatus)[number];
declare expiredTime: Date;
declare data: Data;
id: string;
token: string;
userId: string;
orgId: string;
title: string;
description: string;
status: (typeof UserSecretStatus)[number];
expiredTime: Date;
data: UserSecretData;
constructor(data: UserSecretSelect) {
Object.assign(this, data);
}
/**
* 验证token
* @param token
@@ -57,12 +61,13 @@ export class UserSecret extends Model {
return secretToken;
}
console.log('verifyToken: try to verify as secret key');
const userSecret = await UserSecret.findOne({
where: { token },
});
if (!userSecret) {
const userSecrets = await db.select().from(userSecretsTable).where(eq(userSecretsTable.token, token)).limit(1);
if (userSecrets.length === 0) {
return null; // 如果没有找到对应的用户密钥则返回null
}
const userSecret = new UserSecret(userSecrets[0]);
if (userSecret.isExpired()) {
return null; // 如果用户密钥已过期则返回null
}
@@ -78,19 +83,49 @@ export class UserSecret extends Model {
// 存储到oauth中的token store中
return oauthUser;
}
/**
* 根据主键查找
*/
static async findByPk(id: string): Promise<UserSecret | null> {
const secrets = await db.select().from(userSecretsTable).where(eq(userSecretsTable.id, id)).limit(1);
return secrets.length > 0 ? new UserSecret(secrets[0]) : null;
}
/**
* 根据条件查找一个
*/
static async findOne(where: { token?: string; id?: string }): Promise<UserSecret | null> {
let query = db.select().from(userSecretsTable);
if (where.token) {
query = query.where(eq(userSecretsTable.token, where.token)) as any;
} else if (where.id) {
query = query.where(eq(userSecretsTable.id, where.id)) as any;
}
const secrets = await query.limit(1);
return secrets.length > 0 ? new UserSecret(secrets[0]) : null;
}
/**
* owner 组织用户的 oauthUser
* @returns
*/
async getOauthUser(opts?: { wx?: boolean }) {
const user = await User.findOne({
where: { id: this.userId },
attributes: ['id', 'username', 'type', 'owner', 'data'],
});
let org: User = null;
if (!user) {
const users = await db.select({
id: usersTable.id,
username: usersTable.username,
type: usersTable.type,
owner: usersTable.owner,
data: usersTable.data,
}).from(usersTable).where(eq(usersTable.id, this.userId)).limit(1);
let org: any = null;
if (users.length === 0) {
return null; // 如果没有找到对应的用户则返回null
}
const user = users[0];
const expiredTime = this.expiredTime ? new Date(this.expiredTime).getTime() : null;
const oauthUser: Partial<OauthUser> = {
id: user.id,
@@ -101,11 +136,15 @@ export class UserSecret extends Model {
},
};
if (this.orgId) {
org = await User.findOne({
where: { id: this.orgId },
attributes: ['id', 'username', 'type', 'owner'],
});
if (org) {
const orgUsers = await db.select({
id: usersTable.id,
username: usersTable.username,
type: usersTable.type,
owner: usersTable.owner,
}).from(usersTable).where(eq(usersTable.id, this.orgId)).limit(1);
if (orgUsers.length > 0) {
org = orgUsers[0];
oauthUser.id = org.id;
oauthUser.username = org.username;
oauthUser.type = 'org';
@@ -125,6 +164,7 @@ export class UserSecret extends Model {
const expiredTime = new Date(this.expiredTime);
return now > expiredTime.getTime(); // 如果当前时间大于过期时间,则认为已过期
}
/**
* 检查是否过期如果过期则更新状态为expired
*
@@ -137,7 +177,6 @@ export class UserSecret extends Model {
}
}
try {
const now = Date.now();
const expiredTime = new Date(this.expiredTime);
const isExpired = now > expiredTime.getTime(); // 如果当前时间大于过期时间,则认为已过期
@@ -145,11 +184,11 @@ export class UserSecret extends Model {
this.status = 'active';
const expireTime = UserSecret.getExpiredTime();
this.expiredTime = expireTime;
await this.save()
await this.save();
}
if (this.status !== 'active') {
this.status = 'active';
await this.save()
await this.save();
}
return {
code: 200
@@ -163,6 +202,20 @@ export class UserSecret extends Model {
}
}
}
async save() {
await db.update(userSecretsTable).set({
token: this.token,
userId: this.userId,
orgId: this.orgId,
title: this.title,
description: this.description,
status: this.status,
expiredTime: this.expiredTime ? this.expiredTime.toISOString() : null,
data: this.data,
updatedAt: new Date().toISOString(),
}).where(eq(userSecretsTable.id, this.id));
}
async createNewToken() {
if (this.token) {
await oauth.delToken(this.token);
@@ -172,14 +225,16 @@ export class UserSecret extends Model {
await this.save();
return token;
}
static async createToken() {
let token = oauth.generateSecretKey();
// 确保生成的token是唯一的
while (await UserSecret.findOne({ where: { token } })) {
while (await UserSecret.findOne({ token })) {
token = oauth.generateSecretKey();
}
return token;
}
/**
* 根据 unionid 生成redis的key
* `wxmp:unionid:token:${unionid}`
@@ -189,28 +244,30 @@ export class UserSecret extends Model {
static wxRedisKey(unionid: string) {
return `wxmp:unionid:token:${unionid}`;
}
static getExpiredTime(expireDays?: number) {
const defaultExpireDays = expireDays || 365;
const expireTime = defaultExpireDays * 24 * 60 * 60 * 1000;
return new Date(Date.now() + expireTime)
return new Date(Date.now() + expireTime);
}
static async createSecret(tokenUser: { id: string; uid?: string, title?: string }, expireDays = 365) {
const token = await UserSecret.createToken();
let userId = tokenUser.id;
let orgId: string = null;
let orgId: string | null = null;
if (tokenUser.uid) {
userId = tokenUser.uid;
orgId = tokenUser.id; // 如果是组织用户则uid是组织ID
orgId = tokenUser.id;
}
const userSecret = await UserSecret.create({
const inserted = await db.insert(userSecretsTable).values({
userId,
orgId,
token,
title: tokenUser.title || randomString(6),
expiredTime: UserSecret.getExpiredTime(expireDays),
});
expiredTime: UserSecret.getExpiredTime(expireDays).toISOString(),
}).returning();
return userSecret;
return new UserSecret(inserted[0]);
}
async getPermission(opts: { id: string; uid?: string }) {
@@ -242,8 +299,8 @@ export class UserSecret extends Model {
};
}
if (this.orgId) {
const orgUser = await User.findByPk(this.orgId);
if (orgUser && orgUser.owner === userId) {
const orgUsers = await db.select().from(usersTable).where(eq(usersTable.id, this.orgId)).limit(1);
if (orgUsers.length > 0 && orgUsers[0].owner === userId) {
isAdmin = true;
hasPermission = true;
}
@@ -255,68 +312,5 @@ export class UserSecret extends Model {
};
}
}
/**
* 组织模型在sequelize之后初始化
*/
export const UserSecretInit = async (newSequelize?: any, tableName?: string, sync?: SyncOpts) => {
const sequelize = useContextKey<Sequelize>('sequelize');
UserSecret.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
description: {
type: DataTypes.TEXT,
allowNull: true,
},
status: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: 'active',
comment: '状态',
},
title: {
type: DataTypes.TEXT,
allowNull: true,
},
expiredTime: {
type: DataTypes.DATE,
allowNull: true,
},
token: {
type: DataTypes.STRING,
allowNull: false,
comment: '用户密钥',
defaultValue: '',
},
userId: {
type: DataTypes.UUID,
allowNull: true,
},
data: {
type: DataTypes.JSONB,
allowNull: true,
defaultValue: {},
},
orgId: {
type: DataTypes.UUID,
allowNull: true,
comment: '组织ID',
},
},
{
sequelize: newSequelize || sequelize,
modelName: tableName || 'cf_user_secret',
},
);
if (sync) {
await UserSecret.sync({ alter: true, logging: false, ...sync }).catch((e) => {
console.error('UserSecret sync', e);
});
return UserSecret;
}
return UserSecret;
};
export const UserSecretModel = useContextKey('UserSecretModel', () => UserSecret);