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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user