diff --git a/bun.config.mjs b/bun.config.mjs index 071d6b4..386d22f 100644 --- a/bun.config.mjs +++ b/bun.config.mjs @@ -1,6 +1,5 @@ // @ts-check import { resolvePath } from '@kevisual/use-config'; -import { execSync } from 'node:child_process'; const entry = 'src/index.ts'; const naming = 'app'; @@ -22,16 +21,4 @@ await Bun.build({ // const cmd = `dts -i src/index.ts -o app.d.ts`; // const cmd = `dts -i ${entry} -o ${naming}.d.ts`; -// execSync(cmd, { stdio: 'inherit' }); - -await Bun.build({ - target: 'node', - format: 'esm', - entrypoints: [resolvePath('./src/run.ts', { meta: import.meta })], - outdir: resolvePath('./dist', { meta: import.meta }), - naming: { - entry: `${'run'}.js`, - }, - external, - env: 'KEVISUAL_*', -}); +// execSync(cmd, { stdio: 'inherit' }); \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index e9d32a3..4825af1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,5 @@ import { App } from '@kevisual/router'; import * as redisLib from './modules/redis.ts'; -import * as sequelizeLib from './modules/sequelize.ts'; import { useContextKey } from '@kevisual/context'; import { SimpleRouter } from '@kevisual/router/simple'; import { s3Client, oss as s3Oss } from './modules/s3.ts'; @@ -22,7 +21,6 @@ export const oss = useContextKey( export { s3Client } export const redis = useContextKey('redis', () => redisLib.redis); export const subscriber = useContextKey('subscriber', () => redisLib.subscriber); -export const sequelize = useContextKey('sequelize', () => sequelizeLib.sequelize); export { db }; const init = () => { return new App({ diff --git a/src/auth/drizzle/user.ts b/src/auth/drizzle/user.ts deleted file mode 100644 index 0f5c506..0000000 --- a/src/auth/drizzle/user.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { pgTable, serial, text, varchar, uuid, boolean, jsonb, timestamp } from "drizzle-orm/pg-core"; -import { InferSelectModel, InferInsertModel } from "drizzle-orm"; - -export const users = pgTable('cf_user', { - id: uuid('id').primaryKey().defaultRandom(), - username: text('username').notNull().unique(), - nickname: text('nickname'), - password: text('password'), - email: text('email'), - avatar: text('avatar'), - salt: text('salt'), - description: text('description'), - type: text('type').notNull().default('user'), - owner: uuid('owner'), - orgId: uuid('orgId'), - needChangePassword: boolean('needChangePassword').notNull().default(false), - data: jsonb('data').notNull().default({}), - createdAt: timestamp('createdAt').notNull().defaultNow(), - updatedAt: timestamp('updatedAt').notNull().defaultNow(), - deletedAt: timestamp('deletedAt'), -}); - -// 类型推断 -export type User = InferSelectModel; -export type NewUser = InferInsertModel; - -// 用户数据类型 -export type UserData = { - orgs?: string[]; - wxUnionId?: string; - phone?: string; -}; - -// 用户类型枚举 -export enum UserTypes { - user = 'user', - org = 'org', - visitor = 'visitor', -} -// export class User { - -// } \ No newline at end of file diff --git a/src/auth/models/index.ts b/src/auth/models/index.ts index db38a4b..85efdc5 100644 --- a/src/auth/models/index.ts +++ b/src/auth/models/index.ts @@ -1,3 +1,3 @@ -export { User, UserInit, UserServices, UserModel } from './user.ts'; -export { UserSecretInit, UserSecret } from './user-secret.ts'; -export { OrgInit, Org } from './org.ts'; \ No newline at end of file +export { User, UserServices, UserModel, initializeUser, createDemoUser } from './user.ts'; +export { UserSecret, UserSecretModel } from './user-secret.ts'; +export { Org, OrgModel, OrgRole } from './org.ts'; \ No newline at end of file diff --git a/src/auth/models/org.ts b/src/auth/models/org.ts index 08bd414..d093868 100644 --- a/src/auth/models/org.ts +++ b/src/auth/models/org.ts @@ -1,20 +1,35 @@ -import { DataTypes, Model, Op, Sequelize } from 'sequelize'; import { useContextKey } from '@kevisual/context'; -import { SyncOpts, User } from './user.ts'; +import { User } from './user.ts'; +import { db } from '../../modules/db.ts'; +import { cfOrgs, cfUser } from '../../db/drizzle/schema.ts'; +import { eq, inArray, sql, InferSelectModel, InferInsertModel } from 'drizzle-orm'; + +const orgsTable = cfOrgs; +const usersTable = cfUser; -type AddUserOpts = { - role: string; -}; export enum OrgRole { admin = 'admin', member = 'member', owner = 'owner', } -export class Org extends Model { - declare id: string; - declare username: string; - declare description: string; - declare users: { role: string; uid: string }[]; + +export type OrgUser = { + role: string; + uid: string; +}; + +export type OrgSelect = InferSelectModel; +export type OrgInsert = InferInsertModel; + +export class Org { + id: string; + username: string; + description: string; + users: OrgUser[]; + + constructor(data: OrgSelect) { + Object.assign(this, data); + } /** * operateId 是真实操作者的id * @param user @@ -67,8 +82,8 @@ export class Org extends Model { } else { users.push({ role: opts?.role || 'member', uid: user.id }); } - await Org.update({ users }, { where: { id: this.id } }); - + await db.update(orgsTable).set({ users }).where(eq(orgsTable.id, this.id)); + this.users = users; } /** * operateId 是真实操作者的id @@ -89,7 +104,8 @@ export class Org extends Model { } await user.expireOrgs(); const users = this.users.filter((u) => u.uid !== user.id || u.role === 'owner'); - await Org.update({ users }, { where: { id: this.id } }); + await db.update(orgsTable).set({ users }).where(eq(orgsTable.id, this.id)); + this.users = users; } /** * operateId 是真实操作者的id @@ -112,13 +128,7 @@ export class Org extends Model { } } } - const _users = await User.findAll({ - where: { - id: { - [Op.in]: usersIds, - }, - }, - }); + const _users = await db.select().from(usersTable).where(inArray(usersTable.id, usersIds)); const users = _users.map((u) => { const role = orgUser.find((r) => r.uid === u.id)?.role; @@ -139,46 +149,45 @@ export class Org extends Model { const user = this.users.find((u) => u.uid === userId && u.role === role); return !!user; } -} -/** - * 组织模型,在sequelize之后初始化 - */ -export const OrgInit = async (newSequelize?: any, tableName?: string, sync?: SyncOpts) => { - const sequelize = useContextKey('sequelize'); - 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: newSequelize || sequelize, - modelName: tableName || 'cf_org', - paranoid: true, - }, - ); - if (sync) { - await Org.sync({ alter: true, logging: false, ...sync }).catch((e) => { - console.error('Org sync', e); - }); - return Org; + + /** + * 根据主键查找 + */ + static async findByPk(id: string): Promise { + const orgs = await db.select().from(orgsTable).where(eq(orgsTable.id, id)).limit(1); + return orgs.length > 0 ? new Org(orgs[0]) : null; } - return Org; -}; + + /** + * 根据条件查找一个 + */ + static async findOne(where: { username?: string; id?: string }): Promise { + let query = db.select().from(orgsTable); + + if (where.username) { + query = query.where(eq(orgsTable.username, where.username)) as any; + } else if (where.id) { + query = query.where(eq(orgsTable.id, where.id)) as any; + } + + const orgs = await query.limit(1); + return orgs.length > 0 ? new Org(orgs[0]) : null; + } + + /** + * 创建组织 + */ + static async create(data: { username: string; description?: string; users: OrgUser[] }): Promise { + const inserted = await db.insert(orgsTable).values(data).returning(); + return new Org(inserted[0]); + } + + /** + * 更新组织 + */ + static async update(data: Partial, where: { id: string }) { + await db.update(orgsTable).set(data).where(eq(orgsTable.id, where.id)); + } +} + export const OrgModel = useContextKey('OrgModel', () => Org); diff --git a/src/auth/models/user-secret.ts b/src/auth/models/user-secret.ts index 739a9e3..0ac0219 100644 --- a/src/auth/models/user-secret.ts +++ b/src/auth/models/user-secret.ts @@ -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; +export type UserSecretInsert = InferInsertModel; + export const redis = useContextKey('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 { + 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 { + 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 = { 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'); - 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); diff --git a/src/auth/models/user.ts b/src/auth/models/user.ts index d1a4bd2..64fec25 100644 --- a/src/auth/models/user.ts +++ b/src/auth/models/user.ts @@ -1,45 +1,62 @@ -import { DataTypes, Model, Op, Sequelize } from 'sequelize'; import { nanoid, customAlphabet } from 'nanoid'; import { CustomError } from '@kevisual/router'; -import { Org } from './org.ts'; - import { useContextKey } from '@kevisual/context'; import { Redis } from 'ioredis'; import { oauth } from '../oauth/auth.ts'; import { cryptPwd } from '../oauth/salt.ts'; import { OauthUser } from '../oauth/oauth.ts'; -export const redis = useContextKey('redis'); -import { UserSecret } from './user-secret.ts'; -type UserData = { +import { db } from '../../modules/db.ts'; +import { cfUser, cfOrgs, cfUserSecrets } from '../../db/drizzle/schema.ts'; +import { eq, sql, InferSelectModel, InferInsertModel } from 'drizzle-orm'; + +// 类型定义 +export type UserData = { orgs?: string[]; wxUnionId?: string; phone?: string; canChangeUsername?: boolean; }; + export enum UserTypes { - 'user' = 'user', - 'org' = 'org', - 'visitor' = 'visitor', + user = 'user', + org = 'org', + visitor = 'visitor', } + +export type UserSelect = InferSelectModel; +export type UserInsert = InferInsertModel; +export type OrgSelect = InferSelectModel; + +const usersTable = cfUser; +const orgsTable = cfOrgs; +const userSecretsTable = cfUserSecrets; + +export const redis = useContextKey('redis'); + /** - * 用户模型,在sequelize和Org之后初始化 + * 用户模型,使用 Drizzle ORM */ -export class User extends Model { +export class User { static oauth = oauth; - declare id: string; - declare username: string; - declare nickname: 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; + id: string; + username: string; + nickname: string; + password: string; + salt: string; + needChangePassword: boolean; + description: string; + data: UserData; + type: string; + owner: string; + orgId: string; + email: string; + avatar: string; tokenUser: any; + + constructor(data: UserSelect) { + Object.assign(this, data); + } + setTokenUser(tokenUser: any) { this.tokenUser = tokenUser; } @@ -76,6 +93,7 @@ export class User extends Model { * @returns */ static async verifyToken(token: string) { + const { UserSecret } = await import('./user-secret.ts'); return await UserSecret.verifyToken(token); } /** @@ -88,6 +106,7 @@ export class User extends Model { return { accessToken: token.accessToken, refreshToken: token.refreshToken, token: token.accessToken }; } static async getOauthUser(token: string) { + const { UserSecret } = await import('./user-secret.ts'); return await UserSecret.verifyToken(token); } /** @@ -105,12 +124,16 @@ export class User extends Model { * @returns */ static async getUserByToken(token: string) { + const { UserSecret } = await import('./user-secret.ts'); const oauthUser = await UserSecret.verifyToken(token); if (!oauthUser) { throw new CustomError('Token is invalid. get UserByToken'); } const userId = oauthUser?.uid || oauthUser.id; const user = await User.findByPk(userId); + if (!user) { + throw new CustomError('User not found'); + } user.setTokenUser(oauthUser); return user; } @@ -130,8 +153,33 @@ export class User extends Model { } return allUsers.includes(username); } + + /** + * 根据主键查找用户 + */ + static async findByPk(id: string): Promise { + const users = await db.select().from(usersTable).where(eq(usersTable.id, id)).limit(1); + return users.length > 0 ? new User(users[0]) : null; + } + + /** + * 根据条件查找一个用户 + */ + static async findOne(where: { username?: string; id?: string }): Promise { + let query = db.select().from(usersTable); + + if (where.username) { + query = query.where(eq(usersTable.username, where.username)) as any; + } else if (where.id) { + query = query.where(eq(usersTable.id, where.id)) as any; + } + + const users = await query.limit(1); + return users.length > 0 ? new User(users[0]) : null; + } + static async createUser(username: string, password?: string, description?: string) { - const user = await User.findOne({ where: { username } }); + const user = await User.findOne({ username }); if (user) { throw new CustomError('User already exists'); } @@ -139,10 +187,20 @@ export class User extends Model { let needChangePassword = !password; password = password || '123456'; const cPassword = cryptPwd(password, salt); - return await User.create({ username, password: cPassword, description, salt, needChangePassword }); + + const inserted = await db.insert(usersTable).values({ + username, + password: cPassword, + description, + salt, + needChangePassword, + }).returning(); + + return new User(inserted[0]); } + static async createOrg(username: string, owner: string, description?: string) { - const user = await User.findOne({ where: { username } }); + const user = await User.findOne({ username }); if (user) { throw new CustomError('User already exists'); } @@ -153,24 +211,64 @@ export class User extends Model { if (me.type !== 'user') { throw new CustomError('Owner type is not user'); } + + const { Org } = await import('./org.ts'); 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 }); + const inserted = await db.insert(usersTable).values({ + username, + password: '', + description, + type: 'org', + owner, + orgId: org.id, + }).returning(); + // owner add await redis.del(`user:${me.id}:orgs`); - return newUser; + return new User(inserted[0]); } async createPassword(password: string) { const salt = this.salt; const cPassword = cryptPwd(password, salt); this.password = cPassword; - await this.update({ password: cPassword }); + await db.update(usersTable).set({ password: cPassword }).where(eq(usersTable.id, this.id)); return cPassword; } + checkPassword(password: string) { const salt = this.salt; const cPassword = cryptPwd(password, salt); return this.password === cPassword; } + + /** + * 更新用户 + */ + async update(data: Partial) { + await db.update(usersTable).set(data).where(eq(usersTable.id, this.id)); + Object.assign(this, data); + } + + /** + * 保存用户 + */ + async save() { + await db.update(usersTable).set({ + username: this.username, + nickname: this.nickname, + password: this.password, + email: this.email, + avatar: this.avatar, + salt: this.salt, + description: this.description, + type: this.type, + owner: this.owner, + orgId: this.orgId, + needChangePassword: this.needChangePassword, + data: this.data, + updatedAt: new Date().toISOString(), + }).where(eq(usersTable.id, this.id)); + } /** * 获取用户信息, 需要先设置 tokenUser 或者设置 uid * @param uid 如果存在,则表示是组织,其中uid为真实用户 @@ -217,18 +315,14 @@ export class User extends Model { if (cache) { return JSON.parse(cache) as string[]; } - const orgs = await Org.findAll({ - order: [['updatedAt', 'DESC']], - where: { - users: { - [Op.contains]: [ - { - uid: id, - }, - ], - }, - }, - }); + + // 使用 Drizzle 的 SQL 查询来检查 JSONB 数组 + const orgs = await db + .select() + .from(orgsTable) + .where(sql`${orgsTable.users} @> ${JSON.stringify([{ uid: id }])}::jsonb`) + .orderBy(sql`${orgsTable.updatedAt} DESC`); + 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 @@ -249,93 +343,27 @@ export class User extends Model { } return user?.username; } -} -export type SyncOpts = { - alter?: boolean; - logging?: any; - force?: boolean; -}; -export const UserInit = async (newSequelize?: any, tableName?: string, sync?: SyncOpts) => { - const sequelize = useContextKey('sequelize'); - 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, - }, - 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.JSONB, - defaultValue: {}, - }, - }, - { - sequelize: newSequelize || sequelize, - tableName: tableName || 'cf_user', // codeflow user - paranoid: true, - }, - ); - if (sync) { - await User.sync({ alter: true, logging: true, ...sync }) - .then((res) => { - initializeUser(); - }) - .catch((err) => { - console.error('Sync User error', err); - }); - return User; + /** + * 查找所有符合条件的用户 + */ + static async findAll(options: { where?: any; attributes?: string[] }) { + let query = db.select().from(usersTable); + + if (options.where?.id?.in) { + query = query.where(sql`${usersTable.id} = ANY(${options.where.id.in})`) as any; + } + + const users = await query; + return users.map(u => new User(u)); } - return User; -}; +} + const letter = 'abcdefghijklmnopqrstuvwxyz'; const custom = customAlphabet(letter, 6); + export const initializeUser = async (pwd = custom()) => { - const w = await User.findOne({ where: { username: 'root' }, logging: false }); + const w = await User.findOne({ username: 'root' }); if (!w) { const root = await User.createUser('root', pwd, '系统管理员'); const org = await User.createOrg('admin', root.id, '管理员'); @@ -354,8 +382,9 @@ export const initializeUser = async (pwd = custom()) => { }; } }; + export const createDemoUser = async (username = 'demo', pwd = custom()) => { - const u = await User.findOne({ where: { username }, logging: false }); + const u = await User.findOne({ username }); if (!u) { const user = await User.createUser(username, pwd, 'demo'); console.info('new Users name', user.username, pwd); @@ -371,11 +400,10 @@ export const createDemoUser = async (username = 'demo', pwd = custom()) => { }; } }; -// initializeUser(); export class UserServices extends User { static async loginByPhone(phone: string) { - let user = await User.findOne({ where: { username: phone } }); + let user = await User.findOne({ username: phone }); let isNew = false; if (!user) { user = await User.createUser(phone, phone.slice(-6)); diff --git a/src/db/drizzle/schema.ts b/src/db/drizzle/schema.ts index 9425aca..be48bb7 100644 --- a/src/db/drizzle/schema.ts +++ b/src/db/drizzle/schema.ts @@ -51,7 +51,7 @@ export const appsTrades = pgTable("apps_trades", { ]); export const cfOrgs = pgTable("cf_orgs", { - id: uuid().primaryKey().notNull(), + id: uuid().primaryKey().notNull().defaultRandom(), username: varchar({ length: 255 }).notNull(), users: jsonb().default([]), createdAt: timestamp({ withTimezone: true, mode: 'string' }).notNull().defaultNow(), @@ -81,7 +81,7 @@ export const cfRouterCode = pgTable("cf_router_code", { }); export const cfUser = pgTable("cf_user", { - id: uuid().primaryKey().notNull(), + id: uuid().primaryKey().notNull().defaultRandom(), username: varchar({ length: 255 }).notNull(), password: varchar({ length: 255 }), salt: varchar({ length: 255 }), @@ -102,7 +102,7 @@ export const cfUser = pgTable("cf_user", { ]); export const cfUserSecrets = pgTable("cf_user_secrets", { - id: uuid().primaryKey().notNull(), + id: uuid().primaryKey().notNull().defaultRandom(), description: text(), status: varchar({ length: 255 }).default('active'), title: text(), diff --git a/src/models/user.ts b/src/models/user.ts index e64e49c..cda5721 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -1,18 +1,3 @@ -import { User, UserInit, UserServices } from '../auth/models/index.ts'; -import { UserSecretInit, UserSecret } from '../auth/models/index.ts'; -import { OrgInit } from '../auth/models/index.ts'; -export { User, UserInit, UserServices, UserSecret }; -import { useContextKey } from '@kevisual/context'; -const init = async () => { - await OrgInit(null, null).catch((e) => { - console.error('Org sync', e); - }); - await UserInit(null, null).catch((e) => { - console.error('User sync', e); - }); - await UserSecretInit(null, null).catch((e) => { - console.error('UserSecret sync', e); - }); - useContextKey('models-synced', true); -}; -init(); +import { User, UserServices } from '../auth/models/index.ts'; +import { UserSecret } from '../auth/models/index.ts'; +export { User, UserServices, UserSecret }; \ No newline at end of file diff --git a/src/modules/index.ts b/src/modules/index.ts index 8b50f47..e69de29 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -1 +0,0 @@ -export { sequelize } from './sequelize.ts'; diff --git a/src/modules/sequelize.ts b/src/modules/sequelize.ts deleted file mode 100644 index 5a7cf2a..0000000 --- a/src/modules/sequelize.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Sequelize } from 'sequelize'; -import { config } from './config.ts'; -import { log } from './logger.ts'; -export type PostgresConfig = { - postgres: { - username: string; - password: string; - host: string; - port: number; - database: string; - }; -}; -if (!config.POSTGRES_PASSWORD || !config.POSTGRES_USER) { - log.error('postgres config is required password and user'); - log.error('config', config); - process.exit(1); -} -const postgresConfig = { - username: config.POSTGRES_USER, - password: config.POSTGRES_PASSWORD, - host: config.POSTGRES_HOST || 'localhost', - port: parseInt(config.POSTGRES_PORT || '5432'), - database: config.POSTGRES_DB || 'postgres', -}; -// connect to db -export const sequelize = new Sequelize({ - dialect: 'postgres', - ...postgresConfig, - // logging: false, -}); - -sequelize - .authenticate({ logging: false }) - .then(() => { - log.info('Database connected'); - }) - .catch((err) => { - log.error('Database connection failed', { err, config: postgresConfig }); - process.exit(1); - }); diff --git a/src/routes/app-manager/admin/mv-user-app.ts b/src/routes/app-manager/admin/mv-user-app.ts index 76666ce..ffe9e67 100644 --- a/src/routes/app-manager/admin/mv-user-app.ts +++ b/src/routes/app-manager/admin/mv-user-app.ts @@ -1,12 +1,11 @@ -import { AppModel } from '../module/index.ts'; +import { db, schema } from '@/app.ts'; +import { eq } from 'drizzle-orm'; + export const mvAppFromUserAToUserB = async (userA: string, userB: string) => { - const appList = await AppModel.findAll({ - where: { - user: userA, - }, - }); + const appList = await db.select().from(schema.kvApp).where(eq(schema.kvApp.user, userA)); for (const app of appList) { - app.user = userB; - await app.save(); + await db.update(schema.kvApp) + .set({ user: userB, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvApp.id, app.id)); } }; diff --git a/src/routes/app-manager/domain/domain-self.ts b/src/routes/app-manager/domain/domain-self.ts index 484a3f1..c827752 100644 --- a/src/routes/app-manager/domain/domain-self.ts +++ b/src/routes/app-manager/domain/domain-self.ts @@ -1,6 +1,8 @@ -import { app } from '@/app.ts'; -import { AppModel } from '../module/app.ts'; -import { AppDomainModel } from '../module/app-domain.ts'; +import { app, db, schema } from '@/app.ts'; +import { App, AppData } from '../module/app-drizzle.ts'; +import { AppDomain, AppDomainHelper } from '../module/app-domain-drizzle.ts'; +import { eq, and } from 'drizzle-orm'; +import { randomUUID } from 'crypto'; app .route({ @@ -9,17 +11,17 @@ app }) .define(async (ctx) => { const { domain } = ctx.query.data; - // const query = { - // } - const domainInfo = await AppDomainModel.findOne({ where: { domain } }); + const domainInfos = await db.select().from(schema.kvAppDomain).where(eq(schema.kvAppDomain.domain, domain)).limit(1); + const domainInfo = domainInfos[0]; if (!domainInfo || !domainInfo.appId) { ctx.throw(404, 'app not found'); } - const app = await AppModel.findByPk(domainInfo.appId); - if (!app) { + const apps = await db.select().from(schema.kvApp).where(eq(schema.kvApp.id, domainInfo.appId)).limit(1); + const appFound = apps[0]; + if (!appFound) { ctx.throw(404, 'app not found'); } - ctx.body = app; + ctx.body = appFound; return ctx; }) .addTo(app); @@ -37,7 +39,8 @@ app if (!domain || !appId) { ctx.throw(400, 'domain and appId are required'); } - const domainInfo = await AppDomainModel.create({ domain, appId, uid }); + const newDomains = await db.insert(schema.kvAppDomain).values({ id: randomUUID(), domain, appId, uid }).returning(); + const domainInfo = newDomains[0]; ctx.body = domainInfo; return ctx; }) @@ -59,12 +62,17 @@ app if (!status) { ctx.throw(400, 'status is required'); } - let domainInfo: AppDomainModel | null = null; + let domainInfo: AppDomain | undefined; if (id) { - domainInfo = await AppDomainModel.findByPk(id); + const domains = await db.select().from(schema.kvAppDomain).where(eq(schema.kvAppDomain.id, id)).limit(1); + domainInfo = domains[0]; } if (!domainInfo && domain) { - domainInfo = await AppDomainModel.findOne({ where: { domain, appId } }); + const domains = await db.select().from(schema.kvAppDomain).where(and( + eq(schema.kvAppDomain.domain, domain), + eq(schema.kvAppDomain.appId, appId) + )).limit(1); + domainInfo = domains[0]; } if (!domainInfo) { ctx.throw(404, 'domain not found'); @@ -72,19 +80,23 @@ app if (domainInfo.uid !== uid) { ctx.throw(403, 'domain must be owned by the user'); } - if (!domainInfo.checkCanUpdateStatus(status)) { + if (!AppDomainHelper.checkCanUpdateStatus(domainInfo.status!, status)) { ctx.throw(400, 'domain status can not be updated'); } + const updateData: any = {}; if (status) { - domainInfo.status = status; + updateData.status = status; } - if (appId) { - domainInfo.appId = appId; + updateData.appId = appId; } - await domainInfo.save({ fields: ['status', 'appId'] }); - - ctx.body = domainInfo; + updateData.updatedAt = new Date().toISOString(); + const updateResult = await db.update(schema.kvAppDomain) + .set(updateData) + .where(eq(schema.kvAppDomain.id, domainInfo.id)) + .returning(); + const updatedDomain = updateResult[0]; + ctx.body = updatedDomain; return ctx; }) .addTo(app); diff --git a/src/routes/app-manager/domain/manager.ts b/src/routes/app-manager/domain/manager.ts index ef45159..074168c 100644 --- a/src/routes/app-manager/domain/manager.ts +++ b/src/routes/app-manager/domain/manager.ts @@ -1,7 +1,9 @@ -import { app } from '@/app.ts'; -import { AppDomainModel } from '../module/app-domain.ts'; -import { AppModel } from '../module/app.ts'; +import { app, db, schema } from '@/app.ts'; +import { AppDomain, AppDomainHelper } from '../module/app-domain-drizzle.ts'; +import { App } from '../module/app-drizzle.ts'; import { CustomError } from '@kevisual/router'; +import { eq, or } from 'drizzle-orm'; +import { randomUUID } from 'crypto'; app .route({ @@ -11,10 +13,12 @@ app }) .define(async (ctx) => { const { page = 1, pageSize = 999 } = ctx.query.data || {}; - const { count, rows } = await AppDomainModel.findAndCountAll({ - offset: (page - 1) * pageSize, - limit: pageSize, - }); + const offset = (page - 1) * pageSize; + const rows = await db.select().from(schema.kvAppDomain) + .limit(pageSize) + .offset(offset); + const countResult = await db.select().from(schema.kvAppDomain); + const count = countResult.length; ctx.body = { count, list: rows, pagination: { page, pageSize } }; return ctx; }) @@ -31,11 +35,10 @@ app if (!domain) { ctx.throw(400, 'domain is required'); } - let domainInfo: AppDomainModel; + let domainInfo: AppDomain | undefined; if (id) { - domainInfo = await AppDomainModel.findByPk(id); - } else { - domainInfo = await AppDomainModel.create({ domain }); + const domains = await db.select().from(schema.kvAppDomain).where(eq(schema.kvAppDomain.id, id)).limit(1); + domainInfo = domains[0]; } const checkAppId = async () => { const isUUID = (id: string) => { @@ -45,7 +48,8 @@ app if (!isUUID(rest.appId)) { ctx.throw(400, 'appId is not valid'); } - const appInfo = await AppModel.findByPk(rest.appId); + const apps = await db.select().from(schema.kvApp).where(eq(schema.kvApp.id, rest.appId)).limit(1); + const appInfo = apps[0]; if (!appInfo) { ctx.throw(400, 'appId is not exist'); } @@ -53,24 +57,31 @@ app }; try { if (!domainInfo) { - domainInfo = await AppDomainModel.create({ domain, data: {}, ...rest }); await checkAppId(); + const newDomains = await db.insert(schema.kvAppDomain).values({ id: randomUUID(), domain, data: {}, ...rest }).returning(); + domainInfo = newDomains[0]; } else { if (rest.status && domainInfo.status !== rest.status) { - await domainInfo.clearCache(); + await AppDomainHelper.clearCache(domainInfo.domain!); } await checkAppId(); - await domainInfo.update({ - domain, - data: { - ...domainInfo.data, - ...data, - }, - ...rest, - }); + const domainData = domainInfo.data as any; + const updateResult = await db.update(schema.kvAppDomain) + .set({ + domain, + data: { + ...domainData, + ...data, + }, + ...rest, + updatedAt: new Date().toISOString() + }) + .where(eq(schema.kvAppDomain.id, domainInfo.id)) + .returning(); + domainInfo = updateResult[0]; } ctx.body = domainInfo; - } catch (error) { + } catch (error: any) { if (error.code) { ctx.throw(error.code, error.message); } @@ -94,9 +105,9 @@ app ctx.throw(400, 'id or domain is required'); } if (id) { - await AppDomainModel.destroy({ where: { id }, force: true }); + await db.delete(schema.kvAppDomain).where(eq(schema.kvAppDomain.id, id)); } else { - await AppDomainModel.destroy({ where: { domain }, force: true }); + await db.delete(schema.kvAppDomain).where(eq(schema.kvAppDomain.domain, domain)); } ctx.body = { message: 'delete domain success' }; @@ -115,7 +126,8 @@ app if (!id && !domain) { ctx.throw(400, 'id or domain is required'); } - const domainInfo = await AppDomainModel.findOne({ where: { id } }); + const domains = await db.select().from(schema.kvAppDomain).where(eq(schema.kvAppDomain.id, id)).limit(1); + const domainInfo = domains[0]; if (!domainInfo) { ctx.throw(404, 'domain not found'); } diff --git a/src/routes/app-manager/list.ts b/src/routes/app-manager/list.ts index 7ce46ff..db63ebd 100644 --- a/src/routes/app-manager/list.ts +++ b/src/routes/app-manager/list.ts @@ -1,12 +1,14 @@ import { App, CustomError } from '@kevisual/router'; -import { AppModel, AppListModel } from './module/index.ts'; -import { app, redis } from '@/app.ts'; +import { App as AppType, AppList, AppData, AppHelper } from './module/app-drizzle.ts'; +import { app, redis, db, schema } from '@/app.ts'; import { uniqBy } from 'es-toolkit'; import { getUidByUsername, prefixFix } from './util.ts'; import { deleteFiles, getMinioListAndSetToAppList } from '../file/index.ts'; import { setExpire } from './revoke.ts'; import { User } from '@/models/user.ts'; import { callDetectAppVersion } from './export.ts'; +import { eq, and, desc } from 'drizzle-orm'; +import { randomUUID } from 'crypto'; app .route({ path: 'app', @@ -20,14 +22,13 @@ app if (!data.key) { throw new CustomError('key is required'); } - const list = await AppListModel.findAll({ - order: [['updatedAt', 'DESC']], - where: { - uid: tokenUser.id, - key: data.key, - }, - logging: false, - }); + const list = await db.select() + .from(schema.kvAppList) + .where(and( + eq(schema.kvAppList.uid, tokenUser.id), + eq(schema.kvAppList.key, data.key) + )) + .orderBy(desc(schema.kvAppList.updatedAt)); ctx.body = list.map((item) => prefixFix(item, tokenUser.username)); return ctx; }) @@ -48,33 +49,35 @@ app if (!id && (!key || !version)) { throw new CustomError('id is required'); } - let appListModel: AppListModel; + let appListModel: AppList | undefined; if (id) { - appListModel = await AppListModel.findByPk(id); + const apps = await db.select().from(schema.kvAppList).where(eq(schema.kvAppList.id, id)).limit(1); + appListModel = apps[0]; } else if (key && version) { - appListModel = await AppListModel.findOne({ - where: { - key, - version, - uid: tokenUser.id, - }, - }); + const apps = await db.select().from(schema.kvAppList).where(and( + eq(schema.kvAppList.key, key), + eq(schema.kvAppList.version, version), + eq(schema.kvAppList.uid, tokenUser.id) + )).limit(1); + appListModel = apps[0]; } if (!appListModel && create) { - appListModel = await AppListModel.create({ + const newApps = await db.insert(schema.kvAppList).values({ + id: randomUUID(), key, version, uid: tokenUser.id, data: {}, - }); - const appModel = await AppModel.findOne({ - where: { - key, - uid: tokenUser.id, - }, - }); + }).returning(); + appListModel = newApps[0]; + const appModels = await db.select().from(schema.kvApp).where(and( + eq(schema.kvApp.key, key), + eq(schema.kvApp.uid, tokenUser.id) + )).limit(1); + const appModel = appModels[0]; if (!appModel) { - await AppModel.create({ + await db.insert(schema.kvApp).values({ + id: randomUUID(), key, uid: tokenUser.id, user: tokenUser.username, @@ -88,13 +91,12 @@ app if (res.code !== 200) { ctx.throw(res.message || 'detect version list error'); } - appListModel = await AppListModel.findOne({ - where: { - key, - version, - uid: tokenUser.id, - }, - }); + const apps2 = await db.select().from(schema.kvAppList).where(and( + eq(schema.kvAppList.key, key), + eq(schema.kvAppList.version, version), + eq(schema.kvAppList.uid, tokenUser.id) + )).limit(1); + appListModel = apps2[0]; } if (!appListModel) { ctx.throw('app not found'); @@ -115,10 +117,16 @@ app const tokenUser = ctx.state.tokenUser; const { data, id, ...rest } = ctx.query.data; if (id) { - const app = await AppListModel.findByPk(id); + const apps = await db.select().from(schema.kvAppList).where(eq(schema.kvAppList.id, id)).limit(1); + const app = apps[0]; if (app) { - const newData = { ...app.data, ...data }; - const newApp = await app.update({ data: newData, ...rest }); + const appData = app.data as AppData; + const newData = { ...appData, ...data }; + const updateResult = await db.update(schema.kvAppList) + .set({ data: newData, ...rest, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvAppList.id, id)) + .returning(); + const newApp = updateResult[0]; ctx.body = newApp; setExpire(newApp.id, 'test'); } else { @@ -130,8 +138,8 @@ app if (!rest.key) { throw new CustomError('key is required'); } - const app = await AppListModel.create({ data, ...rest, uid: tokenUser.id }); - ctx.body = app; + const newApps = await db.insert(schema.kvAppList).values({ id: randomUUID(), data, ...rest, uid: tokenUser.id }).returning(); + ctx.body = newApps[0]; return ctx; }) .addTo(app); @@ -149,24 +157,28 @@ app if (!id) { throw new CustomError('id is required'); } - const app = await AppListModel.findByPk(id); + const apps = await db.select().from(schema.kvAppList).where(eq(schema.kvAppList.id, id)).limit(1); + const app = apps[0]; if (!app) { throw new CustomError('app not found'); } - const am = await AppModel.findOne({ where: { key: app.key, uid: app.uid } }); + const ams = await db.select().from(schema.kvApp).where(and( + eq(schema.kvApp.key, app.key), + eq(schema.kvApp.uid, app.uid) + )).limit(1); + const am = ams[0]; if (!am) { throw new CustomError('app not found'); } if (am.version === app.version) { throw new CustomError('app is published'); } - const files = app.data.files || []; + const appData = app.data as AppData; + const files = appData.files || []; if (deleteFile && files.length > 0) { await deleteFiles(files.map((item) => item.path)); } - await app.destroy({ - force: true, - }); + await db.delete(schema.kvAppList).where(eq(schema.kvAppList.id, id)); ctx.body = 'success'; return ctx; }) @@ -205,11 +217,16 @@ app throw new CustomError('user not found'); } } - let am = await AppModel.findOne({ where: { key: appKey, uid } }); + const ams = await db.select().from(schema.kvApp).where(and( + eq(schema.kvApp.key, appKey), + eq(schema.kvApp.uid, uid) + )).limit(1); + let am = ams[0]; let appIsNew = false; if (!am) { appIsNew = true; - am = await AppModel.create({ + const newAms = await db.insert(schema.kvApp).values({ + id: randomUUID(), user: userPrefix, key: appKey, uid, @@ -220,24 +237,40 @@ app data: { files: files || [], }, - }); + }).returning(); + am = newAms[0]; } - let app = await AppListModel.findOne({ where: { version: version, key: appKey, uid: uid } }); + const apps = await db.select().from(schema.kvAppList).where(and( + eq(schema.kvAppList.version, version), + eq(schema.kvAppList.key, appKey), + eq(schema.kvAppList.uid, uid) + )).limit(1); + let app = apps[0]; if (!app) { - app = await AppListModel.create({ + const newApps = await db.insert(schema.kvAppList).values({ + id: randomUUID(), key: appKey, version, uid: uid, data: { files: [], }, - }); + }).returning(); + app = newApps[0]; } - const dataFiles = app.data.files || []; + const appData = app.data as AppData; + const dataFiles = appData.files || []; const newFiles = uniqBy([...dataFiles, ...files], (item) => item.name); - const res = await app.update({ data: { ...app.data, files: newFiles } }); + const updateResult = await db.update(schema.kvAppList) + .set({ data: { ...appData, files: newFiles }, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvAppList.id, app.id)) + .returning(); + const res = updateResult[0]; if (version === am.version && !appIsNew) { - await am.update({ data: { ...am.data, files: newFiles } }); + const amData = am.data as AppData; + await db.update(schema.kvApp) + .set({ data: { ...amData, files: newFiles }, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvApp.id, am.id)); } setExpire(app.id, 'test'); ctx.body = prefixFix(res, userPrefix); @@ -263,9 +296,10 @@ app } const uid = await getUidByUsername(app, ctx, username); - let appList: AppListModel | null = null; + let appList: AppList | undefined = undefined; if (id) { - appList = await AppListModel.findByPk(id); + const appLists = await db.select().from(schema.kvAppList).where(eq(schema.kvAppList.id, id)).limit(1); + appList = appLists[0]; if (appList?.uid !== uid) { ctx.throw('no permission'); } @@ -274,7 +308,12 @@ app if (!version) { ctx.throw('version is required'); } - appList = await AppListModel.findOne({ where: { key: appKey, version, uid } }); + const appLists = await db.select().from(schema.kvAppList).where(and( + eq(schema.kvAppList.key, appKey), + eq(schema.kvAppList.version, version), + eq(schema.kvAppList.uid, uid) + )).limit(1); + appList = appLists[0]; } if (!appList) { ctx.throw('app 未发现'); @@ -287,18 +326,27 @@ app if (res.code !== 200) { ctx.throw(res.message || '检测版本列表失败'); } - appList = await AppListModel.findByPk(appList.id); + const appLists2 = await db.select().from(schema.kvAppList).where(eq(schema.kvAppList.id, appList.id)).limit(1); + appList = appLists2[0]; } if (!appList) { ctx.throw('app 未发现'); } - const files = appList.data.files || []; - const am = await AppModel.findOne({ where: { key: appList.key, uid: uid } }); + const appListData = appList.data as AppData; + const files = appListData.files || []; + const ams = await db.select().from(schema.kvApp).where(and( + eq(schema.kvApp.key, appList.key), + eq(schema.kvApp.uid, uid) + )).limit(1); + const am = ams[0]; if (!am) { ctx.throw('app 未发现'); } - await am.update({ data: { ...am.data, files }, version: appList.version }); + const amData = am.data as AppData; + await db.update(schema.kvApp) + .set({ data: { ...amData, files }, version: appList.version, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvApp.id, am.id)); setExpire(appList.key, am.user); ctx.body = { key: appList.key, @@ -317,11 +365,16 @@ app }) .define(async (ctx) => { const { user, key, id } = ctx.query.data; - let app; + let app: AppType | undefined; if (id) { - app = await AppModel.findByPk(id); + const apps = await db.select().from(schema.kvApp).where(eq(schema.kvApp.id, id)).limit(1); + app = apps[0]; } else if (user && key) { - app = await AppModel.findOne({ where: { user, key } }); + const apps = await db.select().from(schema.kvApp).where(and( + eq(schema.kvApp.user, user), + eq(schema.kvApp.key, key) + )).limit(1); + app = apps[0]; } else { throw new CustomError('user or key is required'); } @@ -364,16 +417,23 @@ app throw new CustomError('appKey and version are required'); } const uid = await getUidByUsername(app, ctx, username); - let appList = await AppListModel.findOne({ where: { key: appKey, version, uid } }); + const appLists = await db.select().from(schema.kvAppList).where(and( + eq(schema.kvAppList.key, appKey), + eq(schema.kvAppList.version, version), + eq(schema.kvAppList.uid, uid) + )).limit(1); + let appList = appLists[0]; if (!appList) { - appList = await AppListModel.create({ + const newAppLists = await db.insert(schema.kvAppList).values({ + id: randomUUID(), key: appKey, version, uid, data: { files: [], }, - }); + }).returning(); + appList = newAppLists[0]; } const checkUsername = username || tokenUser.username; const files = await getMinioListAndSetToAppList({ username: checkUsername, appKey, version }); @@ -383,7 +443,8 @@ app path: item.name, }; }); - let appListFiles = appList.data?.files || []; + const appListData = appList.data as AppData; + let appListFiles = appListData?.files || []; const needAddFiles = newFiles.map((item) => { const findFile = appListFiles.find((appListFile) => appListFile.name === item.name); if (findFile && findFile.name === item.name) { @@ -391,11 +452,20 @@ app } return item; }); - await appList.update({ data: { files: needAddFiles } }); + const updateResult = await db.update(schema.kvAppList) + .set({ data: { files: needAddFiles }, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvAppList.id, appList.id)) + .returning(); + appList = updateResult[0]; setExpire(appList.id, 'test'); - let am = await AppModel.findOne({ where: { key: appKey, uid } }); + const ams = await db.select().from(schema.kvApp).where(and( + eq(schema.kvApp.key, appKey), + eq(schema.kvApp.uid, uid) + )).limit(1); + let am = ams[0]; if (!am) { - am = await AppModel.create({ + const newAms = await db.insert(schema.kvApp).values({ + id: randomUUID(), title: appKey, key: appKey, version: version || '0.0.1', @@ -403,11 +473,19 @@ app uid, data: { files: needAddFiles }, proxy: appKey.includes('center') ? false : true, - }); + }).returning(); + am = newAms[0]; } else { - const appModel = await AppModel.findOne({ where: { key: appKey, version, uid } }); + const appModels = await db.select().from(schema.kvApp).where(and( + eq(schema.kvApp.key, appKey), + eq(schema.kvApp.version, version), + eq(schema.kvApp.uid, uid) + )).limit(1); + const appModel = appModels[0]; if (appModel) { - await appModel.update({ data: { files: needAddFiles } }); + await db.update(schema.kvApp) + .set({ data: { files: needAddFiles }, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvApp.id, appModel.id)); setExpire(appModel.key, appModel.user); } } diff --git a/src/routes/app-manager/module/app-domain-drizzle.ts b/src/routes/app-manager/module/app-domain-drizzle.ts new file mode 100644 index 0000000..179903e --- /dev/null +++ b/src/routes/app-manager/module/app-domain-drizzle.ts @@ -0,0 +1,46 @@ +import { InferSelectModel, InferInsertModel } from 'drizzle-orm'; +import { kvAppDomain } from '@/db/drizzle/schema.ts'; +import { redis } from '@/modules/redis.ts'; + +// 审核,通过,驳回 +const appDomainStatus = ['audit', 'auditReject', 'auditPending', 'running', 'stop'] as const; + +export type AppDomainStatus = (typeof appDomainStatus)[number]; + +// 类型定义 +export type AppDomain = InferSelectModel; +export type NewAppDomain = InferInsertModel; +export type DomainList = AppDomain; + +/** + * AppDomain 辅助函数 + */ +export class AppDomainHelper { + /** + * 检查是否可以更新状态 + */ + static checkCanUpdateStatus(currentStatus: string, newStatus: AppDomainStatus): boolean { + // 原本是运行中,可以改为停止,原本是停止,可以改为运行。 + if (currentStatus === 'running' || currentStatus === 'stop') { + return true; + } + // 原本是审核状态,不能修改。 + return false; + } + + /** + * 清除域名缓存 + */ + static async clearCache(domain: string): Promise { + // 清除缓存 + const cacheKey = `domain:${domain}`; + const checkHas = async () => { + const has = await redis.get(cacheKey); + return has; + }; + const has = await checkHas(); + if (has) { + await redis.set(cacheKey, '', 'EX', 1); + } + } +} diff --git a/src/routes/app-manager/module/app-domain.ts b/src/routes/app-manager/module/app-domain.ts deleted file mode 100644 index 168885f..0000000 --- a/src/routes/app-manager/module/app-domain.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { sequelize } from '../../../modules/sequelize.ts'; -import { DataTypes, Model } from 'sequelize'; -export type DomainList = Partial>; -import { redis } from '../../../modules/redis.ts'; - -// 审核,通过,驳回 -const appDomainStatus = ['audit', 'auditReject', 'auditPending', 'running', 'stop'] as const; - -type AppDomainStatus = (typeof appDomainStatus)[number]; -/** - * 应用域名管理 - */ -export class AppDomainModel extends Model { - declare id: string; - declare domain: string; - declare appId: string; - // 状态, - declare status: AppDomainStatus; - declare uid: string; - declare data: Record; - - declare createdAt: Date; - declare updatedAt: Date; - - checkCanUpdateStatus(newStatus: AppDomainStatus) { - // 原本是运行中,可以改为停止,原本是停止,可以改为运行。 - if (this.status === 'running' || this.status === 'stop') { - return true; - } - // 原本是审核状态,不能修改。 - return false; - } - async clearCache() { - // 清除缓存 - const cacheKey = `domain:${this.domain}`; - const checkHas = async () => { - const has = await redis.get(cacheKey); - return has; - }; - const has = await checkHas(); - if (has) { - await redis.set(cacheKey, '', 'EX', 1); - } - } -} - -AppDomainModel.init( - { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - comment: 'id', - }, - domain: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - }, - data: { - type: DataTypes.JSONB, - allowNull: true, - }, - status: { - type: DataTypes.STRING, - allowNull: false, - defaultValue: 'running', - }, - appId: { - type: DataTypes.STRING, - allowNull: true, - }, - uid: { - type: DataTypes.STRING, - allowNull: true, - }, - }, - { - sequelize, - tableName: 'kv_app_domain', - paranoid: true, - }, -); \ No newline at end of file diff --git a/src/routes/app-manager/module/app-drizzle.ts b/src/routes/app-manager/module/app-drizzle.ts new file mode 100644 index 0000000..78a258d --- /dev/null +++ b/src/routes/app-manager/module/app-drizzle.ts @@ -0,0 +1,80 @@ +import { InferSelectModel, InferInsertModel } from 'drizzle-orm'; +import { kvApp, kvAppList } from '@/db/drizzle/schema.ts'; + +type AppPermissionType = 'public' | 'private' | 'protected'; + +/** + * 共享设置 + * 1. 设置公共可以直接访问 + * 2. 设置受保护需要登录后访问 + * 3. 设置私有只有自己可以访问。\n + * 受保护可以设置密码,设置访问的用户名。切换共享状态后,需要重新设置密码和用户名。 + */ +export interface AppData { + files: { name: string; path: string }[]; + permission?: { + // 访问权限, 字段和minio的权限配置一致 + share: AppPermissionType; // public, private(Only Self), protected(protected, 通过配置访问) + usernames?: string; // 受保护的访问用户名,多个用逗号分隔 + password?: string; // 受保护的访问密码 + 'expiration-time'?: string; // 受保护的访问过期时间 + }; + // 运行环境,browser, node, 或者其他,是数组 + runtime?: string[]; +} + +export enum AppStatus { + running = 'running', + stop = 'stop', +} + +// 类型定义 +export type App = InferSelectModel; +export type NewApp = InferInsertModel; +export type AppList = InferSelectModel; +export type NewAppList = InferInsertModel; + +/** + * App 辅助函数 + */ +export class AppHelper { + /** + * 移动应用到新用户 + */ + static async getNewFiles( + files: { name: string; path: string }[] = [], + opts: { oldUser: string; newUser: string } = { oldUser: '', newUser: '' } + ) { + const { oldUser, newUser } = opts; + const _ = files.map((item) => { + if (item.path.startsWith('http')) { + return item; + } + if (oldUser && item.path.startsWith(oldUser)) { + return item; + } + const paths = item.path.split('/'); + return { + ...item, + path: newUser + '/' + paths.slice(1).join('/'), + }; + }); + return _; + } + + /** + * 获取公开信息(删除敏感数据) + */ + static getPublic(app: App) { + const value = { ...app }; + // 删除不需要的字段 + const data = value.data as AppData; + if (data && data.permission) { + delete data.permission.usernames; + delete data.permission.password; + delete data.permission['expiration-time']; + } + value.data = data; + return value; + } +} diff --git a/src/routes/app-manager/module/app-list.ts b/src/routes/app-manager/module/app-list.ts deleted file mode 100644 index 426ccf3..0000000 --- a/src/routes/app-manager/module/app-list.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { sequelize } from '../../../modules/sequelize.ts'; -import { DataTypes, Model } from 'sequelize'; -import { AppData } from './app.ts'; - -export type AppList = Partial>; - -/** - * APP List 管理 历史版本管理 - */ -export class AppListModel extends Model { - declare id: string; - declare data: AppData; - declare version: string; - declare key: string; - declare uid: string; - declare status: string; -} - -AppListModel.init( - { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - comment: 'id', - }, - data: { - type: DataTypes.JSON, - defaultValue: {}, - }, - version: { - type: DataTypes.STRING, - defaultValue: '', - }, - key: { - type: DataTypes.STRING, - }, - uid: { - type: DataTypes.UUID, - allowNull: true, - }, - status: { - type: DataTypes.STRING, - defaultValue: 'running', - }, - }, - { - sequelize, - tableName: 'kv_app_list', - paranoid: true, - }, -); - -// AppListModel.sync({ alter: true, logging: false }).catch((e) => { -// console.error('AppListModel sync', e); -// }); diff --git a/src/routes/app-manager/module/app.ts b/src/routes/app-manager/module/app.ts deleted file mode 100644 index 11b6973..0000000 --- a/src/routes/app-manager/module/app.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { sequelize } from '../../../modules/sequelize.ts'; -import { DataTypes, Model } from 'sequelize'; - -type AppPermissionType = 'public' | 'private' | 'protected'; -/** - * 共享设置 - * 1. 设置公共可以直接访问 - * 2. 设置受保护需要登录后访问 - * 3. 设置私有只有自己可以访问。\n - * 受保护可以设置密码,设置访问的用户名。切换共享状态后,需要重新设置密码和用户名。 - */ -export interface AppData { - files: { name: string; path: string }[]; - permission?: { - // 访问权限, 字段和minio的权限配置一致 - share: AppPermissionType; // public, private(Only Self), protected(protected, 通过配置访问) - usernames?: string; // 受保护的访问用户名,多个用逗号分隔 - password?: string; // 受保护的访问密码 - 'expiration-time'?: string; // 受保护的访问过期时间 - }; - // 运行环境,browser, node, 或者其他,是数组 - runtime?: string[]; -} -export enum AppStatus { - running = 'running', - stop = 'stop', -} -export type App = Partial>; - -/** - * APP 管理 - */ -export class AppModel extends Model { - declare id: string; - declare data: AppData; - declare title: string; - declare description: string; - declare version: string; - declare key: string; - declare uid: string; - declare pid: string; - // 是否是history路由代理模式。静态的直接转minio,而不需要缓存下来。 - declare proxy: boolean; - declare user: string; - declare status: string; - static async moveToNewUser(oldUserName: string, newUserName: string) { - const appIds = await AppModel.findAll({ - where: { - user: oldUserName, - }, - attributes: ['id'], - }); - for (const app of appIds) { - const appData = await AppModel.findByPk(app.id); - appData.user = newUserName; - const data = appData.data; - data.files = await AppModel.getNewFiles(data.files, { - oldUser: oldUserName, - newUser: newUserName, - }); - appData.data = { ...data }; - await appData.save({ fields: ['data', 'user'] }); - } - } - static async getNewFiles(files: { name: string; path: string }[] = [], opts: { oldUser: string; newUser: string } = { oldUser: '', newUser: '' }) { - const { oldUser, newUser } = opts; - const _ = files.map((item) => { - if (item.path.startsWith('http')) { - return item; - } - if (oldUser && item.path.startsWith(oldUser)) { - return item; - } - const paths = item.path.split('/'); - return { - ...item, - path: newUser + '/' + paths.slice(1).join('/'), - }; - }); - return _; - } - - async getPublic() { - const value = this.toJSON(); - // 删除不需要的字段 - const data = value.data; - if (data && data.permission) { - delete data.permission.usernames; - delete data.permission.password; - delete data.permission['expiration-time']; - } - value.data = data; - return value; - } -} -AppModel.init( - { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - comment: 'id', - }, - title: { - type: DataTypes.STRING, - defaultValue: '', - }, - description: { - type: DataTypes.STRING, - defaultValue: '', - }, - data: { - type: DataTypes.JSONB, - defaultValue: {}, - }, - version: { - type: DataTypes.STRING, - defaultValue: '', - }, - key: { - type: DataTypes.STRING, - // 和 uid 组合唯一 - }, - uid: { - type: DataTypes.UUID, - allowNull: true, - }, - pid: { - type: DataTypes.UUID, - allowNull: true, - }, - proxy: { - type: DataTypes.BOOLEAN, - defaultValue: false, - }, - user: { - type: DataTypes.STRING, - allowNull: true, - }, - status: { - type: DataTypes.STRING, - defaultValue: 'running', // stop, running - }, - }, - { - sequelize, - tableName: 'kv_app', - paranoid: true, - indexes: [ - { - unique: true, - fields: ['key', 'uid'], - }, - ], - }, -); - -// AppModel.sync({ alter: true, logging: false }).catch((e) => { -// console.error('AppModel sync', e); -// }); diff --git a/src/routes/app-manager/module/index.ts b/src/routes/app-manager/module/index.ts index 11d6baa..7d02516 100644 --- a/src/routes/app-manager/module/index.ts +++ b/src/routes/app-manager/module/index.ts @@ -1,2 +1,3 @@ -export * from './app-list.ts'; -export * from './app.ts'; +// Drizzle 模型(推荐使用) +export * from './app-domain-drizzle.ts' +export * from './app-drizzle.ts' \ No newline at end of file diff --git a/src/routes/app-manager/public/list.ts b/src/routes/app-manager/public/list.ts index ab86ac5..4568bd5 100644 --- a/src/routes/app-manager/public/list.ts +++ b/src/routes/app-manager/public/list.ts @@ -1,6 +1,6 @@ -import { app } from '@/app.ts'; -import { AppModel } from '../module/index.ts'; +import { app, db, schema } from '@/app.ts'; import { ConfigPermission } from '@kevisual/permission'; +import { eq, desc, asc } from 'drizzle-orm'; // curl http://localhost:4005/api/router?path=app&key=public-list // TODO: @@ -11,23 +11,20 @@ app }) .define(async (ctx) => { const { username = 'root', status = 'running', page = 1, pageSize = 100, order = 'DESC' } = ctx.query.data || {}; - const { rows, count } = await AppModel.findAndCountAll({ - where: { - status, - user: username, - }, - attributes: { - exclude: [], - }, - order: [['updatedAt', order]], - limit: pageSize, - offset: (page - 1) * pageSize, - distinct: true, - logging: false, - }); + const offset = (page - 1) * pageSize; + const apps = await db.select().from(schema.kvApp) + .where(eq(schema.kvApp.user, username)) + .orderBy(order === 'DESC' ? desc(schema.kvApp.updatedAt) : asc(schema.kvApp.updatedAt)) + .limit(pageSize) + .offset(offset); + // Note: Drizzle doesn't have a direct equivalent to findAndCountAll + // We need to do a separate count query + const countResult = await db.select({ count: schema.kvApp.id }).from(schema.kvApp) + .where(eq(schema.kvApp.user, username)); + const count = countResult.length; ctx.body = { - list: rows.map((item) => { - return ConfigPermission.getDataPublicPermission(item.toJSON()); + list: apps.map((item) => { + return ConfigPermission.getDataPublicPermission(item); }), pagination: { total: count, diff --git a/src/routes/app-manager/public/post.ts b/src/routes/app-manager/public/post.ts index 2cb5393..9bb1ce7 100644 --- a/src/routes/app-manager/public/post.ts +++ b/src/routes/app-manager/public/post.ts @@ -1,9 +1,7 @@ -import { app } from '@/app.ts'; -import { AppModel } from '../module/index.ts'; -import { AppListModel } from '../module/index.ts'; +import { app, db, schema } from '@/app.ts'; +import { randomUUID } from 'crypto'; import { oss } from '@/app.ts'; import { User } from '@/models/user.ts'; -import { permission } from 'process'; import { customAlphabet } from 'nanoid'; import dayjs from 'dayjs'; @@ -65,7 +63,8 @@ app path: urlPath, }, ]; - const appModel = await AppModel.create({ + const appModels = await db.insert(schema.kvApp).values({ + id: randomUUID(), title, description, version, @@ -80,15 +79,18 @@ app }, files: files, }, - }); - const appVersionModel = await AppListModel.create({ + }).returning(); + const appModel = appModels[0]; + const appVersionModels = await db.insert(schema.kvAppList).values({ + id: randomUUID(), data: { files: files, }, version: appModel.version, key: appModel.key, uid: appModel.uid, - }); + }).returning(); + const appVersionModel = appVersionModels[0]; ctx.body = { url: `/${username}/${key}/`, diff --git a/src/routes/app-manager/user-app.ts b/src/routes/app-manager/user-app.ts index 9cf4828..062db06 100644 --- a/src/routes/app-manager/user-app.ts +++ b/src/routes/app-manager/user-app.ts @@ -1,7 +1,8 @@ -import { AppModel, AppListModel } from './module/index.ts'; -import { app } from '@/app.ts'; +import { App, AppList, AppData, AppHelper } from './module/app-drizzle.ts'; +import { app, db, schema } from '@/app.ts'; import { setExpire } from './revoke.ts'; import { deleteFileByPrefix } from '../file/index.ts'; +import { eq, and, desc } from 'drizzle-orm'; app .route({ @@ -12,15 +13,24 @@ app }) .define(async (ctx) => { const tokenUser = ctx.state.tokenUser; - const list = await AppModel.findAll({ - order: [['updatedAt', 'DESC']], - where: { - uid: tokenUser.id, - }, - attributes: { - exclude: ['data'], - }, - }); + const list = await db.select({ + id: schema.kvApp.id, + title: schema.kvApp.title, + description: schema.kvApp.description, + version: schema.kvApp.version, + key: schema.kvApp.key, + uid: schema.kvApp.uid, + pid: schema.kvApp.pid, + proxy: schema.kvApp.proxy, + user: schema.kvApp.user, + status: schema.kvApp.status, + createdAt: schema.kvApp.createdAt, + updatedAt: schema.kvApp.updatedAt, + deletedAt: schema.kvApp.deletedAt, + }) + .from(schema.kvApp) + .where(eq(schema.kvApp.uid, tokenUser.id)) + .orderBy(desc(schema.kvApp.updatedAt)); ctx.body = list; return ctx; }) @@ -40,14 +50,18 @@ app if (!id && !key) { ctx.throw(500, 'id is required'); } - let am: AppModel; + let am: App | undefined; if (id) { - am = await AppModel.findByPk(id); + const apps = await db.select().from(schema.kvApp).where(eq(schema.kvApp.id, id)).limit(1); + am = apps[0]; if (!am) { ctx.throw(500, 'app not found'); } } else { - am = await AppModel.findOne({ where: { key, uid: tokenUser.id } }); + const apps = await db.select().from(schema.kvApp) + .where(and(eq(schema.kvApp.key, key), eq(schema.kvApp.uid, tokenUser.id))) + .limit(1); + am = apps[0]; if (!am) { ctx.throw(500, 'app not found'); } @@ -71,21 +85,27 @@ app const { data, id, user, ...rest } = ctx.query.data; if (id) { - const app = await AppModel.findByPk(id); + const apps = await db.select().from(schema.kvApp).where(eq(schema.kvApp.id, id)).limit(1); + const app = apps[0]; if (app) { - const newData = { ...app.data, ...data }; + const appData = app.data as AppData; + const newData = { ...appData, ...data }; if (app.user !== tokenUser.username) { rest.user = tokenUser.username; let files = newData?.files || []; if (files.length > 0) { - files = await AppModel.getNewFiles(files, { oldUser: app.user, newUser: tokenUser.username }); + files = await AppHelper.getNewFiles(files, { oldUser: app.user!, newUser: tokenUser.username }); } newData.files = files; } - const newApp = await app.update({ data: newData, ...rest }); + const updateResult = await db.update(schema.kvApp) + .set({ data: newData, ...rest, updatedAt: new Date().toISOString() }) + .where(eq(schema.kvApp.id, id)) + .returning(); + const newApp = updateResult[0]; ctx.body = newApp; if (app.status !== 'running' || data?.share || rest?.status) { - setExpire(newApp.key, app.user); + setExpire(newApp.key!, app.user!); } } else { ctx.throw(500, 'app not found'); @@ -95,17 +115,19 @@ app if (!rest.key) { ctx.throw(500, 'key is required'); } - const findApp = await AppModel.findOne({ where: { key: rest.key, uid: tokenUser.id } }); - if (findApp) { + const findApps = await db.select().from(schema.kvApp) + .where(and(eq(schema.kvApp.key, rest.key), eq(schema.kvApp.uid, tokenUser.id))) + .limit(1); + if (findApps.length > 0) { ctx.throw(500, 'key already exists'); } - const app = await AppModel.create({ + const newApps = await db.insert(schema.kvApp).values({ data: { files: [] }, ...rest, uid: tokenUser.id, user: tokenUser.username, - }); - ctx.body = app; + }).returning(); + ctx.body = newApps[0]; return ctx; }) .addTo(app); @@ -124,16 +146,18 @@ app if (!id) { ctx.throw(500, 'id is required'); } - const am = await AppModel.findByPk(id); + const apps = await db.select().from(schema.kvApp).where(eq(schema.kvApp.id, id)).limit(1); + const am = apps[0]; if (!am) { ctx.throw(500, 'app not found'); } if (am.uid !== tokenUser.id) { ctx.throw(500, 'app not found'); } - const list = await AppListModel.findAll({ where: { key: am.key, uid: tokenUser.id } }); - await am.destroy({ force: true }); - await Promise.all(list.map((item) => item.destroy({ force: true }))); + const list = await db.select().from(schema.kvAppList) + .where(and(eq(schema.kvAppList.key, am.key!), eq(schema.kvAppList.uid, tokenUser.id))); + await db.delete(schema.kvApp).where(eq(schema.kvApp.id, id)); + await Promise.all(list.map((item) => db.delete(schema.kvAppList).where(eq(schema.kvAppList.id, item.id)))); if (deleteFile) { const username = tokenUser.username; await deleteFileByPrefix(`${username}/${am.key}`); @@ -154,13 +178,13 @@ app if (!id) { ctx.throw(500, 'id is required'); } - const am = await AppListModel.findByPk(id); + const apps = await db.select().from(schema.kvAppList).where(eq(schema.kvAppList.id, id)).limit(1); + const am = apps[0]; if (!am) { ctx.throw(500, 'app not found'); } - const amJson = am.toJSON(); ctx.body = { - ...amJson, + ...am, proxy: true, }; }) diff --git a/src/routes/file-listener/index.ts b/src/routes/file-listener/index.ts index 83ec5cd..5f4e429 100644 --- a/src/routes/file-listener/index.ts +++ b/src/routes/file-listener/index.ts @@ -1 +1 @@ -import './list.ts'; +// import './list.ts'; diff --git a/src/routes/file-listener/list.ts b/src/routes/file-listener/list.ts index dfbd3d8..dcdd0d7 100644 --- a/src/routes/file-listener/list.ts +++ b/src/routes/file-listener/list.ts @@ -1,107 +1,107 @@ -import { Op } from 'sequelize'; -import { app } from '@/app.ts'; -import { FileSyncModel } from './model.ts'; -app - .route({ - path: 'file-listener', - key: 'list', - middleware: ['auth'], - description: '获取用户的某一个文件夹下的所有的列表的数据', - }) - .define(async (ctx) => { - const tokenUser = ctx.state.tokenUser; - const username = tokenUser.username; - const { page = 1, pageSize = 20, sort = 'DESC' } = ctx.query; - let { prefix } = ctx.query; - if (prefix) { - if (typeof prefix !== 'string') { - ctx.throw(400, 'prefix must be a string'); - } - if (prefix.startsWith('/')) { - prefix = prefix.slice(1); // Remove leading slash if present - } - if (!prefix.startsWith(username + '/')) { - ctx.throw(400, 'prefix must start with the your username:', username); - } - } - const searchWhere = prefix - ? { - [Op.or]: [{ name: { [Op.like]: `${prefix}%` } }], - } - : {}; +// import { Op } from 'sequelize'; +// import { app } from '@/app.ts'; +// import { FileSyncModel } from './model.ts'; +// app +// .route({ +// path: 'file-listener', +// key: 'list', +// middleware: ['auth'], +// description: '获取用户的某一个文件夹下的所有的列表的数据', +// }) +// .define(async (ctx) => { +// const tokenUser = ctx.state.tokenUser; +// const username = tokenUser.username; +// const { page = 1, pageSize = 20, sort = 'DESC' } = ctx.query; +// let { prefix } = ctx.query; +// if (prefix) { +// if (typeof prefix !== 'string') { +// ctx.throw(400, 'prefix must be a string'); +// } +// if (prefix.startsWith('/')) { +// prefix = prefix.slice(1); // Remove leading slash if present +// } +// if (!prefix.startsWith(username + '/')) { +// ctx.throw(400, 'prefix must start with the your username:', username); +// } +// } +// const searchWhere = prefix +// ? { +// [Op.or]: [{ name: { [Op.like]: `${prefix}%` } }], +// } +// : {}; - const { rows: files, count } = await FileSyncModel.findAndCountAll({ - where: { - ...searchWhere, - }, - offset: (page - 1) * pageSize, - limit: pageSize, - order: [['updatedAt', sort]], - }); - const getPublicFiles = (files: FileSyncModel[]) => { - return files.map((file) => { - const value = file.toJSON(); - const stat = value.stat || {}; - delete stat.password; - return { - ...value, - stat: stat, - }; - }); - }; +// const { rows: files, count } = await FileSyncModel.findAndCountAll({ +// where: { +// ...searchWhere, +// }, +// offset: (page - 1) * pageSize, +// limit: pageSize, +// order: [['updatedAt', sort]], +// }); +// const getPublicFiles = (files: FileSyncModel[]) => { +// return files.map((file) => { +// const value = file.toJSON(); +// const stat = value.stat || {}; +// delete stat.password; +// return { +// ...value, +// stat: stat, +// }; +// }); +// }; - ctx.body = { - list: getPublicFiles(files), - pagination: { - page, - current: page, - pageSize, - total: count, - }, - }; - }) - .addTo(app); +// ctx.body = { +// list: getPublicFiles(files), +// pagination: { +// page, +// current: page, +// pageSize, +// total: count, +// }, +// }; +// }) +// .addTo(app); -app - .route({ - path: 'file-listener', - key: 'get', - middleware: ['auth'], - }) - .define(async (ctx) => { - const tokenUser = ctx.state.tokenUser; - const username = tokenUser.username; - const { id, name, hash } = ctx.query.data || {}; +// app +// .route({ +// path: 'file-listener', +// key: 'get', +// middleware: ['auth'], +// }) +// .define(async (ctx) => { +// const tokenUser = ctx.state.tokenUser; +// const username = tokenUser.username; +// const { id, name, hash } = ctx.query.data || {}; - if (!id && !name && !hash) { - ctx.throw(400, 'id, name or hash is required'); - } - let fileSync: FileSyncModel | null = null; - if (id) { - fileSync = await FileSyncModel.findByPk(id); - } - if (name && !fileSync) { - fileSync = await FileSyncModel.findOne({ - where: { - name, - hash, - }, - }); - } - if (!fileSync && hash) { - fileSync = await FileSyncModel.findOne({ - where: { - name: { - [Op.like]: `${username}/%`, - }, - hash, - }, - }); - } +// if (!id && !name && !hash) { +// ctx.throw(400, 'id, name or hash is required'); +// } +// let fileSync: FileSyncModel | null = null; +// if (id) { +// fileSync = await FileSyncModel.findByPk(id); +// } +// if (name && !fileSync) { +// fileSync = await FileSyncModel.findOne({ +// where: { +// name, +// hash, +// }, +// }); +// } +// if (!fileSync && hash) { +// fileSync = await FileSyncModel.findOne({ +// where: { +// name: { +// [Op.like]: `${username}/%`, +// }, +// hash, +// }, +// }); +// } - if (!fileSync || !fileSync.name.startsWith(`${username}/`)) { - ctx.throw(404, 'NotFoundFile'); - } - ctx.body = fileSync; - }) - .addTo(app); +// if (!fileSync || !fileSync.name.startsWith(`${username}/`)) { +// ctx.throw(404, 'NotFoundFile'); +// } +// ctx.body = fileSync; +// }) +// .addTo(app); diff --git a/src/routes/file-listener/model.ts b/src/routes/file-listener/model.ts index 8858ddf..7892a4f 100644 --- a/src/routes/file-listener/model.ts +++ b/src/routes/file-listener/model.ts @@ -1,3 +1,3 @@ -import { FileSyncModel } from '@kevisual/file-listener/src/file-sync/model.ts'; -import type { FileSyncModelType } from '@kevisual/file-listener/src/file-sync/model.ts'; -export { FileSyncModel, FileSyncModelType }; +// import { FileSyncModel } from '@kevisual/file-listener/src/file-sync/model.ts'; +// import type { FileSyncModelType } from '@kevisual/file-listener/src/file-sync/model.ts'; +// export { FileSyncModel, FileSyncModelType }; diff --git a/src/routes/index.ts b/src/routes/index.ts index b594577..539f435 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -8,8 +8,6 @@ import './micro-app/index.ts'; import './config/index.ts'; -// import './file-listener/index.ts'; - import './mark/index.ts'; import './light-code/index.ts'; diff --git a/src/routes/micro-app/list.ts b/src/routes/micro-app/list.ts index 06f285a..04e6d48 100644 --- a/src/routes/micro-app/list.ts +++ b/src/routes/micro-app/list.ts @@ -1,8 +1,9 @@ -import { app } from '@/app.ts'; +import { app, db, schema } from '@/app.ts'; import { appPathCheck, installApp } from './module/install-app.ts'; import { manager } from './manager-app.ts'; import { selfRestart } from '@/modules/self-restart.ts'; -import { AppListModel } from '../app-manager/module/index.ts'; +import { AppList } from '../app-manager/module/index.ts'; +import { eq, and } from 'drizzle-orm'; // curl http://localhost:4002/api/router?path=micro-app&key=deploy // 把对应的应用安装到系统的apps目录下,并解压,然后把配置项写入数据库配置 // key 是应用的唯一标识,和package.json中的key一致,绑定关系 @@ -26,17 +27,17 @@ app if (data.username && username === 'admin') { username = data.username; } - let microApp: AppListModel; + let microApp: AppList | undefined; if (!microApp && id) { - microApp = await AppListModel.findByPk(id); + const apps = await db.select().from(schema.kvAppList).where(eq(schema.kvAppList.id, id)).limit(1); + microApp = apps[0]; } if (!microApp && postAppKey) { - microApp = await AppListModel.findOne({ - where: { - key: postAppKey, - version: postVersion, - }, - }); + const apps = await db.select().from(schema.kvAppList).where(and( + eq(schema.kvAppList.key, postAppKey), + eq(schema.kvAppList.version, postVersion) + )).limit(1); + microApp = apps[0]; } if (!microApp) { diff --git a/src/routes/micro-app/manager-app.ts b/src/routes/micro-app/manager-app.ts index 4bcb288..9a3732c 100644 --- a/src/routes/micro-app/manager-app.ts +++ b/src/routes/micro-app/manager-app.ts @@ -5,7 +5,6 @@ import path from 'path'; const assistantAppsConfig = path.join(process.cwd(), 'assistant-apps-config.json'); const isExist = fileIsExist(assistantAppsConfig); export const existDenpend = [ - 'sequelize', // commonjs 'pg', // commonjs '@kevisual/router', // 共享模块 'ioredis', // commonjs diff --git a/src/routes/types.ts b/src/routes/types.ts deleted file mode 100644 index dda5edb..0000000 --- a/src/routes/types.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../old-apps/container/type.ts' \ No newline at end of file diff --git a/src/routes/user/admin/user.ts b/src/routes/user/admin/user.ts index ebeca65..accf790 100644 --- a/src/routes/user/admin/user.ts +++ b/src/routes/user/admin/user.ts @@ -1,9 +1,10 @@ -import { app } from '@/app.ts'; +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 { AppModel } from '@/routes/app-manager/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) => { @@ -43,7 +44,7 @@ export const toChangeName = async (opts: { id: number; newName: string; admin?: data.canChangeUsername = false; user.data = data; try { - await user.save({ fields: ['username', 'data'] }); + await user.save(); // 迁移文件数据 await backupUserA(oldName, user.id); // 备份文件数据 await mvUserAToUserB(oldName, newName, true); // 迁移文件数据 @@ -53,7 +54,15 @@ export const toChangeName = async (opts: { id: number; newName: string; admin?: const type = user.type === 'org' ? 'org' : 'user'; await User.clearUserToken(user.id, type); // 清除旧token } - await AppModel.moveToNewUser(oldName, newName); // 更新用户数据 + // 更新应用数据中的用户名 + 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'); @@ -93,7 +102,7 @@ app ctx.throw(400, 'Username is required'); } checkUsername(username); - const user = await User.findOne({ where: { username } }); + const user = await User.findOne({ username }); ctx.body = { id: user?.id, @@ -138,7 +147,7 @@ app ctx.throw(400, 'Username is required'); } checkUsername(username); - const findUserByUsername = await User.findOne({ where: { username } }); + const findUserByUsername = await User.findOne({ username }); if (findUserByUsername) { ctx.throw(400, 'Username already exists'); } @@ -165,7 +174,7 @@ app if (!user) { ctx.throw(404, 'User not found'); } - await user.destroy(); + await db.delete(schema.cfUser).where(eq(schema.cfUser.id, user.id)); backupUserA(user.username, user.id); deleteUser(user.username); // TODO: EXPIRE 删除token diff --git a/src/routes/user/list.ts b/src/routes/user/list.ts index 6dda8e7..422f1cf 100644 --- a/src/routes/user/list.ts +++ b/src/routes/user/list.ts @@ -1,8 +1,9 @@ -import { app } from '@/app.ts'; +import { app, db, schema } from '@/app.ts'; import { User } from '@/models/user.ts'; import { CustomError } from '@kevisual/router'; import { checkUsername } from './admin/user.ts'; import { nanoid } from 'nanoid'; +import { sql } from 'drizzle-orm'; app .route({ @@ -11,11 +12,15 @@ app middleware: ['auth'], }) .define(async (ctx) => { - const users = await User.findAll({ - attributes: ['id', 'username', 'description', 'needChangePassword'], - order: [['updatedAt', 'DESC']], - logging: false, - }); + const users = await db + .select({ + id: schema.cfUser.id, + username: schema.cfUser.username, + description: schema.cfUser.description, + needChangePassword: schema.cfUser.needChangePassword, + }) + .from(schema.cfUser) + .orderBy(sql`${schema.cfUser.updatedAt} DESC`); ctx.body = users; }) .addTo(app); @@ -71,7 +76,7 @@ app throw new CustomError(400, 'username is required'); } checkUsername(username); - const findUserByUsername = await User.findOne({ where: { username } }); + const findUserByUsername = await User.findOne({ username }); if (findUserByUsername) { throw new CustomError(400, 'username already exists'); } diff --git a/src/routes/user/org-user/list.ts b/src/routes/user/org-user/list.ts index 417d735..c948f34 100644 --- a/src/routes/user/org-user/list.ts +++ b/src/routes/user/org-user/list.ts @@ -39,7 +39,7 @@ app if (userId) { user = await User.findByPk(userId); } else if (username) { - user = await User.findOne({ where: { username } }); + user = await User.findOne({ username }); } if (!user) { ctx.throw('用户不存在'); diff --git a/src/routes/user/org.ts b/src/routes/user/org.ts index f6d470f..08b203e 100644 --- a/src/routes/user/org.ts +++ b/src/routes/user/org.ts @@ -1,7 +1,7 @@ -import { app } from '@/app.ts'; +import { app, db, schema } from '@/app.ts'; import { Org } from '@/models/org.ts'; import { User } from '@/models/user.ts'; -import { Op } from 'sequelize'; +import { sql, eq } from 'drizzle-orm'; app .route({ @@ -11,19 +11,11 @@ app }) .define(async (ctx) => { const tokenUser = ctx.state.tokenUser; - const list = await Org.findAll({ - order: [['updatedAt', 'DESC']], - where: { - users: { - [Op.contains]: [ - { - uid: tokenUser.id, - }, - ], - }, - }, - logging: false, - }); + const list = await db + .select() + .from(schema.cfOrgs) + .where(sql`${schema.cfOrgs.users} @> ${JSON.stringify([{ uid: tokenUser.id }])}::jsonb`) + .orderBy(sql`${schema.cfOrgs.updatedAt} DESC`); ctx.body = list; return ctx; @@ -49,14 +41,16 @@ app ctx.throw('org not found'); } org.description = description; - await org.save(); - const user = await User.findOne({ where: { username } }); - user.description = description; - await user.save(); + await db.update(schema.cfOrgs).set({ description }).where(eq(schema.cfOrgs.id, org.id)); + const user = await User.findOne({ username }); + if (user) { + user.description = description; + await user.save(); + } ctx.body = { - id: user.id, - username: user.username, - description: user.description, + id: user?.id, + username: user?.username, + description: user?.description, }; return; } @@ -100,11 +94,11 @@ app if (owner.uid !== tokenUser.id) { ctx.throw('Permission denied'); } - await org.destroy({ force: true }); - const orgUser = await User.findOne({ - where: { username }, - }); - await orgUser.destroy({ force: true }); + await db.delete(schema.cfOrgs).where(eq(schema.cfOrgs.id, org.id)); + const orgUser = await User.findOne({ username }); + if (orgUser) { + await db.delete(schema.cfUser).where(eq(schema.cfUser.id, orgUser.id)); + } ctx.body = 'success'; }) .addTo(app); @@ -160,12 +154,7 @@ app }; return; } - const usernameUser = await User.findOne({ - where: { username }, - attributes: { - exclude: ['password', 'salt'], - }, - }); + const usernameUser = await User.findOne({ username }); if (!usernameUser) { ctx.body = { diff --git a/src/routes/user/secret-key/list.ts b/src/routes/user/secret-key/list.ts index 3c04117..0570524 100644 --- a/src/routes/user/secret-key/list.ts +++ b/src/routes/user/secret-key/list.ts @@ -1,7 +1,8 @@ -import { Op } from 'sequelize'; import { User, UserSecret } from '@/models/user.ts'; -import { app } from '@/app.ts'; +import { app, db, schema } from '@/app.ts'; import { redis } from '@/app.ts'; +import { eq, and, or, like, isNull, sql } from 'drizzle-orm'; + app .route({ path: 'secret', @@ -11,29 +12,63 @@ app .define(async (ctx) => { const tokenUser = ctx.state.tokenUser; const { page = 1, pageSize = 100, search, sort = 'DESC', orgId, showToken = false } = ctx.query; - const searchWhere: Record = search - ? { - [Op.or]: [{ title: { [Op.like]: `%${search}%` } }, { description: { [Op.like]: `%${search}%` } }], - } - : {}; - if (orgId) { - searchWhere.orgId = orgId; - } else { - searchWhere.orgId = null; + + let conditions = [eq(schema.cfUserSecrets.userId, tokenUser.userId)]; + + if (search) { + conditions.push( + or( + like(schema.cfUserSecrets.title, `%${search}%`), + like(schema.cfUserSecrets.description, `%${search}%`) + ) + ); } - const excludeFields = showToken ? [] : ['token']; - const { rows: secrets, count } = await UserSecret.findAndCountAll({ - where: { - userId: tokenUser.userId, - ...searchWhere, - }, - offset: (page - 1) * pageSize, - limit: pageSize, - attributes: { - exclude: excludeFields, // Exclude sensitive token field - }, - order: [['updatedAt', sort]], - }); + + if (orgId) { + conditions.push(eq(schema.cfUserSecrets.orgId, orgId)); + } else { + conditions.push(isNull(schema.cfUserSecrets.orgId)); + } + + const selectFields = showToken ? { + id: schema.cfUserSecrets.id, + userId: schema.cfUserSecrets.userId, + orgId: schema.cfUserSecrets.orgId, + title: schema.cfUserSecrets.title, + description: schema.cfUserSecrets.description, + status: schema.cfUserSecrets.status, + expiredTime: schema.cfUserSecrets.expiredTime, + data: schema.cfUserSecrets.data, + token: schema.cfUserSecrets.token, + createdAt: schema.cfUserSecrets.createdAt, + updatedAt: schema.cfUserSecrets.updatedAt, + } : { + id: schema.cfUserSecrets.id, + userId: schema.cfUserSecrets.userId, + orgId: schema.cfUserSecrets.orgId, + title: schema.cfUserSecrets.title, + description: schema.cfUserSecrets.description, + status: schema.cfUserSecrets.status, + expiredTime: schema.cfUserSecrets.expiredTime, + data: schema.cfUserSecrets.data, + createdAt: schema.cfUserSecrets.createdAt, + updatedAt: schema.cfUserSecrets.updatedAt, + }; + + const secrets = await db + .select(selectFields) + .from(schema.cfUserSecrets) + .where(and(...conditions)) + .orderBy(sort === 'DESC' ? sql`${schema.cfUserSecrets.updatedAt} DESC` : sql`${schema.cfUserSecrets.updatedAt} ASC`) + .limit(pageSize) + .offset((page - 1) * pageSize); + + const countResult = await db + .select({ count: sql`count(*)` }) + .from(schema.cfUserSecrets) + .where(and(...conditions)); + + const count = Number(countResult[0]?.count || 0); ctx.body = { list: secrets, @@ -69,12 +104,14 @@ app ctx.throw(403, 'No permission'); } } else if (title) { - secret = await UserSecret.findOne({ - where: { - userId: tokenUser.userId, - title, - }, - }); + const secrets = await db + .select() + .from(schema.cfUserSecrets) + .where(and(eq(schema.cfUserSecrets.userId, tokenUser.userId), eq(schema.cfUserSecrets.title, title))) + .limit(1); + if (secrets.length > 0) { + secret = new UserSecret(secrets[0]); + } } if (!secret) { secret = await UserSecret.createSecret({ @@ -83,10 +120,12 @@ app }); isNew = true; } - if (secret) { - secret = await secret.update({ - ...rest, - }); + if (secret && Object.keys(rest).length > 0) { + await db + .update(schema.cfUserSecrets) + .set({ ...rest, updatedAt: new Date().toISOString() }) + .where(eq(schema.cfUserSecrets.id, secret.id)); + secret = await UserSecret.findByPk(secret.id); } ctx.body = secret; @@ -112,12 +151,14 @@ app secret = await UserSecret.findByPk(id); } if (!secret && title) { - secret = await UserSecret.findOne({ - where: { - userId: tokenUser.userId, - title, - }, - }); + const secrets = await db + .select() + .from(schema.cfUserSecrets) + .where(and(eq(schema.cfUserSecrets.userId, tokenUser.userId), eq(schema.cfUserSecrets.title, title))) + .limit(1); + if (secrets.length > 0) { + secret = new UserSecret(secrets[0]); + } if (!secret) { ctx.throw(404, 'Secret not found'); } @@ -130,7 +171,7 @@ app ctx.throw(403, 'No permission'); } - await secret.destroy(); + await db.delete(schema.cfUserSecrets).where(eq(schema.cfUserSecrets.id, secret.id)); ctx.body = secret; }) .addTo(app); @@ -154,12 +195,14 @@ app if (id) { secret = await UserSecret.findByPk(id); } else if (title) { - secret = await UserSecret.findOne({ - where: { - userId: tokenUser.userId, - title, - }, - }); + const secrets = await db + .select() + .from(schema.cfUserSecrets) + .where(and(eq(schema.cfUserSecrets.userId, tokenUser.userId), eq(schema.cfUserSecrets.title, title))) + .limit(1); + if (secrets.length > 0) { + secret = new UserSecret(secrets[0]); + } } if (!secret) { @@ -194,23 +237,22 @@ app.route({ ctx.body = 'success' return; } - const user = await User.findOne({ - where: { - data: { - wxUnionId: unionid - } - } - }) + const users = await db + .select() + .from(schema.cfUser) + .where(sql`${schema.cfUser.data}->>'wxUnionId' = ${unionid}`) + .limit(1); + const user = users.length > 0 ? new User(users[0]) : null; if (!user) { ctx.throw(404, '请关注公众号《人生可视化助手》后再操作'); return } - let secretKey = await UserSecret.findOne({ - where: { - userId: user.id, - title: 'wxmp-notify-token' - } - }); + const secretKeys = await db + .select() + .from(schema.cfUserSecrets) + .where(and(eq(schema.cfUserSecrets.userId, user.id), eq(schema.cfUserSecrets.title, 'wxmp-notify-token'))) + .limit(1); + let secretKey = secretKeys.length > 0 ? new UserSecret(secretKeys[0]) : null; if (!secretKey) { secretKey = await UserSecret.createSecret({ id: user.id, title: 'wxmp-notify-token' }); } diff --git a/src/routes/user/update.ts b/src/routes/user/update.ts index eee8452..71f36ae 100644 --- a/src/routes/user/update.ts +++ b/src/routes/user/update.ts @@ -39,18 +39,13 @@ app if (avatar) { updateData.avatar = avatar; } - await user.update( - { - ...updateData, - data: { - ...user.data, - ...data, - }, + await user.update({ + ...updateData, + data: { + ...user.data, + ...data, }, - { - fields: ['nickname', 'avatar', 'data'], - }, - ); + }); user.setTokenUser(tokenUser); ctx.body = await user.getInfo(); }) diff --git a/src/run.ts b/src/run.ts deleted file mode 100644 index 96b224f..0000000 --- a/src/run.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { program } from './program.ts'; - -// -import './scripts/change-user-pwd.ts'; -import './scripts/list-app.ts'; - -// - -program.parse(process.argv); diff --git a/src/test/remove-app-list.ts b/src/test/remove-app-list.ts index 40b0fc5..fc789bc 100644 --- a/src/test/remove-app-list.ts +++ b/src/test/remove-app-list.ts @@ -1,7 +1,7 @@ -import { AppListModel } from '../routes/app-manager/module/index.ts'; +import { db, schema } from '../app.ts'; const main = async () => { - const list = await AppListModel.findAll(); + const list = await db.select().from(schema.kvAppList); console.log(list.map((item) => item.key)); }; diff --git a/src/type.ts b/src/type.ts deleted file mode 100644 index 6b41b82..0000000 --- a/src/type.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ContainerData } from './routes/types.ts'; - -export { ContainerData };