feat: user org and fix bugs
This commit is contained in:
		| @@ -10,7 +10,7 @@ import { bucketName } from '@/modules/minio.ts'; | |||||||
| import { getContentType } from '@/utils/get-content-type.ts'; | import { getContentType } from '@/utils/get-content-type.ts'; | ||||||
| import { User } from '@/models/user.ts'; | import { User } from '@/models/user.ts'; | ||||||
| const { tokenSecret } = useConfig<{ tokenSecret: string }>(); | const { tokenSecret } = useConfig<{ tokenSecret: string }>(); | ||||||
| const filePath = useFileStore('upload'); | const filePath = useFileStore('upload', { needExists: true }); | ||||||
| // curl -X POST http://localhost:4000/api/upload -F "file=@readme.md" | // curl -X POST http://localhost:4000/api/upload -F "file=@readme.md" | ||||||
| // curl -X POST http://localhost:4000/api/upload \ | // curl -X POST http://localhost:4000/api/upload \ | ||||||
| //   -F "file=@readme.md" \ | //   -F "file=@readme.md" \ | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								src/models/org.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/models/org.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | import { sequelize } from '../modules/sequelize.ts'; | ||||||
|  | import { DataTypes, Model } from 'sequelize'; | ||||||
|  |  | ||||||
|  | export class Org extends Model { | ||||||
|  |   declare id: string; | ||||||
|  |   declare username: string; | ||||||
|  |   declare description: string; | ||||||
|  |   declare users: { role: string; uid: string }[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Org.init( | ||||||
|  |   { | ||||||
|  |     id: { | ||||||
|  |       type: DataTypes.UUID, | ||||||
|  |       primaryKey: true, | ||||||
|  |       defaultValue: DataTypes.UUIDV4, | ||||||
|  |     }, | ||||||
|  |     username: { | ||||||
|  |       type: DataTypes.STRING, | ||||||
|  |       allowNull: false, | ||||||
|  |       unique: true, | ||||||
|  |     }, | ||||||
|  |     description: { | ||||||
|  |       type: DataTypes.STRING, | ||||||
|  |       allowNull: true, | ||||||
|  |     }, | ||||||
|  |     users: { | ||||||
|  |       type: DataTypes.JSONB, | ||||||
|  |       allowNull: true, | ||||||
|  |       defaultValue: [], | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     sequelize, | ||||||
|  |     modelName: 'cf_org', | ||||||
|  |     paranoid: true, | ||||||
|  |   }, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Org.sync({ alter: true, logging: false }).catch((e) => { | ||||||
|  |   console.error('Org sync', e); | ||||||
|  | }); | ||||||
| @@ -1,13 +1,18 @@ | |||||||
| import { useConfig } from '@abearxiong/use-config'; | import { useConfig } from '@abearxiong/use-config'; | ||||||
| import { sequelize } from '@/modules/sequelize.ts'; | import { sequelize } from '@/modules/sequelize.ts'; | ||||||
| import { DataTypes, Model } from 'sequelize'; | import { DataTypes, Model, Op } from 'sequelize'; | ||||||
| import { createToken, checkToken } from '@abearxiong/auth/token'; | import { createToken, checkToken } from '@abearxiong/auth/token'; | ||||||
| import { cryptPwd } from '@abearxiong/auth'; | import { cryptPwd } from '@abearxiong/auth'; | ||||||
| import { nanoid } from 'nanoid'; | import { nanoid } from 'nanoid'; | ||||||
| import { CustomError } from '@abearxiong/router'; | import { CustomError } from '@abearxiong/router'; | ||||||
|  | import { Org } from './org.ts'; | ||||||
|  | import { redis } from '@/app.ts'; | ||||||
|  |  | ||||||
| const config = useConfig<{ tokenSecret: string }>(); | const config = useConfig<{ tokenSecret: string }>(); | ||||||
|  |  | ||||||
|  | type UserData = { | ||||||
|  |   orgs?: string[]; | ||||||
|  | }; | ||||||
| export class User extends Model { | export class User extends Model { | ||||||
|   declare id: string; |   declare id: string; | ||||||
|   declare username: string; |   declare username: string; | ||||||
| @@ -15,12 +20,16 @@ export class User extends Model { | |||||||
|   declare salt: string; |   declare salt: string; | ||||||
|   declare needChangePassword: boolean; |   declare needChangePassword: boolean; | ||||||
|   declare description: string; |   declare description: string; | ||||||
|   declare data: any; |   declare data: UserData; | ||||||
|   async createToken() { |   declare type: string; // user | org | ||||||
|  |   declare owner: string; | ||||||
|  |   declare orgId: string; | ||||||
|  |   declare email: string; | ||||||
|  |   async createToken(uid?: string) { | ||||||
|     const { id, username } = this; |     const { id, username } = this; | ||||||
|     const expireTime = 60 * 60 * 24 * 7; // 7 days |     const expireTime = 60 * 60 * 24 * 7; // 7 days | ||||||
|     const now = new Date().getTime(); |     const now = new Date().getTime(); | ||||||
|     const token = await createToken({ id, username }, config.tokenSecret); |     const token = await createToken({ id, username, uid }, config.tokenSecret); | ||||||
|     return { token, expireTime: now + expireTime }; |     return { token, expireTime: now + expireTime }; | ||||||
|   } |   } | ||||||
|   static async verifyToken(token: string) { |   static async verifyToken(token: string) { | ||||||
| @@ -28,8 +37,8 @@ export class User extends Model { | |||||||
|     const tokenUser = ct.payload; |     const tokenUser = ct.payload; | ||||||
|     return tokenUser; |     return tokenUser; | ||||||
|   } |   } | ||||||
|   static createUser(username: string, password?: string, description?: string) { |   static async createUser(username: string, password?: string, description?: string) { | ||||||
|     const user = User.findOne({ where: { username } }); |     const user = await User.findOne({ where: { username } }); | ||||||
|     if (user) { |     if (user) { | ||||||
|       throw new CustomError('User already exists'); |       throw new CustomError('User already exists'); | ||||||
|     } |     } | ||||||
| @@ -37,7 +46,25 @@ export class User extends Model { | |||||||
|     let needChangePassword = !password; |     let needChangePassword = !password; | ||||||
|     password = password || '123456'; |     password = password || '123456'; | ||||||
|     const cPassword = cryptPwd(password, salt); |     const cPassword = cryptPwd(password, salt); | ||||||
|     return User.create({ username, password: cPassword, description, salt, needChangePassword }); |     return await User.create({ username, password: cPassword, description, salt, needChangePassword }); | ||||||
|  |   } | ||||||
|  |   static async createOrg(username: string, owner: string, description?: string) { | ||||||
|  |     const user = await User.findOne({ where: { username } }); | ||||||
|  |     if (user) { | ||||||
|  |       throw new CustomError('User already exists'); | ||||||
|  |     } | ||||||
|  |     const me = await User.findByPk(owner); | ||||||
|  |     if (!me) { | ||||||
|  |       throw new CustomError('Owner not found'); | ||||||
|  |     } | ||||||
|  |     if (me.type !== 'user') { | ||||||
|  |       throw new CustomError('Owner type is not user'); | ||||||
|  |     } | ||||||
|  |     const org = await Org.create({ username, description, users: [{ uid: owner, role: 'owner' }] }); | ||||||
|  |     const newUser = await User.create({ username, password: '', description, type: 'org', owner, orgId: org.id }); | ||||||
|  |     // owner add | ||||||
|  |     await redis.del(`user:${me.id}:orgs`); | ||||||
|  |     return newUser; | ||||||
|   } |   } | ||||||
|   createPassword(password: string) { |   createPassword(password: string) { | ||||||
|     const salt = this.salt; |     const salt = this.salt; | ||||||
| @@ -45,6 +72,44 @@ export class User extends Model { | |||||||
|     this.password = cPassword; |     this.password = cPassword; | ||||||
|     return cPassword; |     return cPassword; | ||||||
|   } |   } | ||||||
|  |   checkPassword(password: string) { | ||||||
|  |     const salt = this.salt; | ||||||
|  |     const cPassword = cryptPwd(password, salt); | ||||||
|  |     return this.password === cPassword; | ||||||
|  |   } | ||||||
|  |   async getInfo() { | ||||||
|  |     const orgs = await this.getOrgs(); | ||||||
|  |     return { | ||||||
|  |       id: this.id, | ||||||
|  |       username: this.username, | ||||||
|  |       description: this.description, | ||||||
|  |       needChangePassword: this.needChangePassword, | ||||||
|  |       type: this.type, | ||||||
|  |       orgs, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |   async getOrgs() { | ||||||
|  |     const id = this.id; | ||||||
|  |     const cache = await redis.get(`user:${id}:orgs`); | ||||||
|  |     if (cache) { | ||||||
|  |       return JSON.parse(cache); | ||||||
|  |     } | ||||||
|  |     const orgs = await Org.findAll({ | ||||||
|  |       order: [['updatedAt', 'DESC']], | ||||||
|  |       where: { | ||||||
|  |         users: { | ||||||
|  |           [Op.contains]: [ | ||||||
|  |             { | ||||||
|  |               uid: id, | ||||||
|  |             }, | ||||||
|  |           ], | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |     const orgNames = orgs.map((org) => org.username); | ||||||
|  |     await redis.set(`user:${id}:orgs`, JSON.stringify(orgNames), 'EX', 60 * 60); // 1 hour | ||||||
|  |     return orgNames; | ||||||
|  |   } | ||||||
| } | } | ||||||
| User.init( | User.init( | ||||||
|   { |   { | ||||||
| @@ -60,15 +125,29 @@ User.init( | |||||||
|     }, |     }, | ||||||
|     password: { |     password: { | ||||||
|       type: DataTypes.STRING, |       type: DataTypes.STRING, | ||||||
|       allowNull: false, |       allowNull: true, | ||||||
|  |     }, | ||||||
|  |     email: { | ||||||
|  |       type: DataTypes.STRING, | ||||||
|  |       allowNull: true, | ||||||
|     }, |     }, | ||||||
|     salt: { |     salt: { | ||||||
|       type: DataTypes.STRING, |       type: DataTypes.STRING, | ||||||
|       allowNull: false, |       allowNull: true, | ||||||
|     }, |     }, | ||||||
|     description: { |     description: { | ||||||
|       type: DataTypes.STRING, |       type: DataTypes.STRING, | ||||||
|     }, |     }, | ||||||
|  |     type: { | ||||||
|  |       type: DataTypes.STRING, | ||||||
|  |       defaultValue: 'user', | ||||||
|  |     }, | ||||||
|  |     owner: { | ||||||
|  |       type: DataTypes.UUID, | ||||||
|  |     }, | ||||||
|  |     orgId: { | ||||||
|  |       type: DataTypes.UUID, | ||||||
|  |     }, | ||||||
|     needChangePassword: { |     needChangePassword: { | ||||||
|       type: DataTypes.BOOLEAN, |       type: DataTypes.BOOLEAN, | ||||||
|       defaultValue: false, |       defaultValue: false, | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { app, redis } from '@/app.ts'; | |||||||
| import _ from 'lodash'; | import _ from 'lodash'; | ||||||
| import { prefixFix } from './util.ts'; | import { prefixFix } from './util.ts'; | ||||||
| import { deleteFiles } from '../file/index.ts'; | import { deleteFiles } from '../file/index.ts'; | ||||||
|  | import { setExpire } from './revoke.ts'; | ||||||
| app | app | ||||||
|   .route({ |   .route({ | ||||||
|     path: 'app', |     path: 'app', | ||||||
| @@ -23,6 +23,7 @@ app | |||||||
|         uid: tokenUser.id, |         uid: tokenUser.id, | ||||||
|         key: data.key, |         key: data.key, | ||||||
|       }, |       }, | ||||||
|  |       logging: false, | ||||||
|     }); |     }); | ||||||
|     ctx.body = list.map((item) => prefixFix(item, tokenUser.username)); |     ctx.body = list.map((item) => prefixFix(item, tokenUser.username)); | ||||||
|     return ctx; |     return ctx; | ||||||
| @@ -189,15 +190,7 @@ app | |||||||
|       throw new CustomError('app not found'); |       throw new CustomError('app not found'); | ||||||
|     } |     } | ||||||
|     await am.update({ data: { ...am.data, files }, version: app.version }); |     await am.update({ data: { ...am.data, files }, version: app.version }); | ||||||
|     // |     setExpire(app.key, am.user); | ||||||
|     const keys = await redis.keys('user:app:exist:*'); |  | ||||||
|     console.log('keys', keys); |  | ||||||
|     const expireKey = 'user:app:exist:' + `${app.key}:${am.user}`; |  | ||||||
|     console.log('expireKey', expireKey); |  | ||||||
|     await redis.set(expireKey, 'v', 'EX', 2); |  | ||||||
|     await new Promise((resolve) => setTimeout(resolve, 2100)); |  | ||||||
|     const keys2 = await redis.keys('user:app:exist:*'); |  | ||||||
|     console.log('keys2', keys2); |  | ||||||
|     ctx.body = 'success'; |     ctx.body = 'success'; | ||||||
|   }) |   }) | ||||||
|   .addTo(app); |   .addTo(app); | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								src/routes/app-manager/revoke.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/routes/app-manager/revoke.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | import { redis } from '@/app.ts'; | ||||||
|  |  | ||||||
|  | export const setExpire = async (key: string, user: string) => { | ||||||
|  |   const expireKey = 'user:app:exist:' + `${key}:${user}`; | ||||||
|  |   await redis.set(expireKey, 'v', 'EX', 2); | ||||||
|  | }; | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { CustomError } from '@abearxiong/router'; | import { CustomError } from '@abearxiong/router'; | ||||||
| import { AppModel, AppListModel } from './module/index.ts'; | import { AppModel, AppListModel } from './module/index.ts'; | ||||||
| import { app } from '@/app.ts'; | import { app } from '@/app.ts'; | ||||||
|  | import { setExpire } from './revoke.ts'; | ||||||
|  |  | ||||||
| app | app | ||||||
|   .route({ |   .route({ | ||||||
| @@ -68,6 +69,9 @@ app | |||||||
|         const newData = { ...app.data, ...data }; |         const newData = { ...app.data, ...data }; | ||||||
|         const newApp = await app.update({ data: newData, ...rest }); |         const newApp = await app.update({ data: newData, ...rest }); | ||||||
|         ctx.body = newApp; |         ctx.body = newApp; | ||||||
|  |         if (app.status !== 'running') { | ||||||
|  |           setExpire(newApp.key, app.user); | ||||||
|  |         } | ||||||
|       } else { |       } else { | ||||||
|         throw new CustomError('app not found'); |         throw new CustomError('app not found'); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
| import './list.ts' | import './list.ts'; | ||||||
|  | import './org.ts'; | ||||||
|  |  | ||||||
|  | import './me.ts'; | ||||||
|   | |||||||
| @@ -3,7 +3,11 @@ import { User } from '@/models/user.ts'; | |||||||
| import { CustomError } from '@abearxiong/router'; | import { CustomError } from '@abearxiong/router'; | ||||||
|  |  | ||||||
| app | app | ||||||
|   .route('user', 'list') |   .route({ | ||||||
|  |     path: 'user', | ||||||
|  |     key: 'list', | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|   .define(async (ctx) => { |   .define(async (ctx) => { | ||||||
|     const users = await User.findAll({ |     const users = await User.findAll({ | ||||||
|       attributes: ['id', 'username', 'description', 'needChangePassword'], |       attributes: ['id', 'username', 'description', 'needChangePassword'], | ||||||
| @@ -14,72 +18,21 @@ app | |||||||
|   }) |   }) | ||||||
|   .addTo(app); |   .addTo(app); | ||||||
|  |  | ||||||
| app |  | ||||||
|   .route('user', 'login') |  | ||||||
|   .define(async (ctx) => { |  | ||||||
|     const { username, password } = ctx.query; |  | ||||||
|     const user = await User.findOne({ where: { username } }); |  | ||||||
|     if (!user) { |  | ||||||
|       new CustomError(401, 'User not found'); |  | ||||||
|     } |  | ||||||
|     if (user.password !== password) { |  | ||||||
|       new CustomError(401, 'Password error'); |  | ||||||
|     } |  | ||||||
|     const token = await user.createToken(); |  | ||||||
|     ctx.body = token; |  | ||||||
|   }) |  | ||||||
|   .addTo(app); |  | ||||||
|  |  | ||||||
| app | app | ||||||
|   .route('user', 'auth') |   .route({ | ||||||
|   .define(async (ctx) => { |     path: 'user', | ||||||
|     const { checkToken: token } = ctx.query; |     key: 'update', | ||||||
|     try { |  | ||||||
|       const result = await User.verifyToken(token); |  | ||||||
|       ctx.body = result || {}; |  | ||||||
|     } catch (e) { |  | ||||||
|       new CustomError(401, 'Token InValid '); |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
|   .addTo(app); |  | ||||||
|  |  | ||||||
| app |  | ||||||
|   .route('user', 'updateSelf', { |  | ||||||
|     middleware: ['auth'], |     middleware: ['auth'], | ||||||
|   }) |   }) | ||||||
|   .define(async (ctx) => { |   .define(async (ctx) => { | ||||||
|     const { username, password, description } = ctx.query; |     const tokenUser = ctx.state.tokenUser; | ||||||
|     const state = ctx.state?.tokenUser || {}; |     const { id, username, password, description } = ctx.query.data || {}; | ||||||
|     const { id } = state; |  | ||||||
|     const user = await User.findByPk(id); |     const user = await User.findByPk(id); | ||||||
|     if (!user) { |     if (user.id !== tokenUser.id) { | ||||||
|       throw new CustomError(500, 'user not found'); |       throw new CustomError(401, 'Permission denied'); | ||||||
|     } |     } | ||||||
|     if (username) { |  | ||||||
|       user.username = username; |  | ||||||
|     } |  | ||||||
|     if (password) { |  | ||||||
|       user.createPassword(password); |  | ||||||
|     } |  | ||||||
|     if (description) { |  | ||||||
|       user.description = description; |  | ||||||
|     } |  | ||||||
|     await user.save(); |  | ||||||
|     ctx.body = { |  | ||||||
|       id: user.id, |  | ||||||
|       username: user.username, |  | ||||||
|       description: user.description, |  | ||||||
|       needChangePassword: user.needChangePassword, |  | ||||||
|     }; |  | ||||||
|   }) |  | ||||||
|   .addTo(app); |  | ||||||
| app |  | ||||||
|   .route('user', 'update', { |  | ||||||
|     middleware: ['auth'], |  | ||||||
|   }) |  | ||||||
|   .define(async (ctx) => { |  | ||||||
|     const { id, username, password, description } = ctx.query; |  | ||||||
|     const user = await User.findByPk(id); |  | ||||||
|     if (!user) { |     if (!user) { | ||||||
|       throw new CustomError(500, 'user not found'); |       throw new CustomError(500, 'user not found'); | ||||||
|     } |     } | ||||||
| @@ -102,8 +55,15 @@ app | |||||||
|   }) |   }) | ||||||
|   .addTo(app); |   .addTo(app); | ||||||
|  |  | ||||||
| app.route('user', 'add').define(async (ctx) => { | app | ||||||
|   const { username, password, description } = ctx.query; |   .route({ | ||||||
|  |     path: 'user', | ||||||
|  |     key: 'add', | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const tokenUser = ctx.state.tokenUser; | ||||||
|  |     const { username, password, description } = ctx.query.data || {}; | ||||||
|     if (!username) { |     if (!username) { | ||||||
|       throw new CustomError(400, 'username is required'); |       throw new CustomError(400, 'username is required'); | ||||||
|     } |     } | ||||||
| @@ -116,4 +76,4 @@ app.route('user', 'add').define(async (ctx) => { | |||||||
|       needChangePassword: user.needChangePassword, |       needChangePassword: user.needChangePassword, | ||||||
|       token, |       token, | ||||||
|     }; |     }; | ||||||
| }); |   }); | ||||||
|   | |||||||
							
								
								
									
										124
									
								
								src/routes/user/me.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/routes/user/me.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | import { app } from '@/app.ts'; | ||||||
|  | import { Org } from '@/models/org.ts'; | ||||||
|  | import { User } from '@/models/user.ts'; | ||||||
|  | import { CustomError } from '@abearxiong/router'; | ||||||
|  |  | ||||||
|  | app | ||||||
|  |   .route({ | ||||||
|  |     path: 'user', | ||||||
|  |     key: 'me', | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const state = ctx.state?.tokenUser || {}; | ||||||
|  |     const { id } = state; | ||||||
|  |     const user = await User.findByPk(id); | ||||||
|  |     if (!user) { | ||||||
|  |       throw new CustomError(500, 'user not found'); | ||||||
|  |     } | ||||||
|  |     ctx.body = await user.getInfo(); | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
|  | app | ||||||
|  |   .route({ | ||||||
|  |     path: 'user', | ||||||
|  |     key: 'login', | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const { username, email, password } = ctx.query; | ||||||
|  |     if (!username && !email) { | ||||||
|  |       throw new CustomError(400, 'username or email is required'); | ||||||
|  |     } | ||||||
|  |     let user: User | null = null; | ||||||
|  |     if (username) { | ||||||
|  |       user = await User.findOne({ where: { username } }); | ||||||
|  |     } | ||||||
|  |     if (!user && email) { | ||||||
|  |       user = await User.findOne({ where: { email } }); | ||||||
|  |     } | ||||||
|  |     if (!user) { | ||||||
|  |       throw new CustomError(500, 'Login Failed'); | ||||||
|  |     } | ||||||
|  |     if (!user.checkPassword(password)) { | ||||||
|  |       throw new CustomError(500, 'Password error'); | ||||||
|  |     } | ||||||
|  |     const token = await user.createToken(); | ||||||
|  |     ctx.body = token; | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
|  |  | ||||||
|  | app | ||||||
|  |   .route('user', 'auth') | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const { checkToken: token } = ctx.query; | ||||||
|  |     try { | ||||||
|  |       const result = await User.verifyToken(token); | ||||||
|  |       ctx.body = result || {}; | ||||||
|  |     } catch (e) { | ||||||
|  |       throw new CustomError(401, 'Token InValid '); | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
|  |  | ||||||
|  | app | ||||||
|  |   .route('user', 'updateSelf', { | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const { username, password, description } = ctx.query; | ||||||
|  |     const state = ctx.state?.tokenUser || {}; | ||||||
|  |     const { id } = state; | ||||||
|  |     const user = await User.findByPk(id); | ||||||
|  |     if (!user) { | ||||||
|  |       throw new CustomError(500, 'user not found'); | ||||||
|  |     } | ||||||
|  |     if (username) { | ||||||
|  |       user.username = username; | ||||||
|  |     } | ||||||
|  |     if (password) { | ||||||
|  |       user.createPassword(password); | ||||||
|  |     } | ||||||
|  |     if (description) { | ||||||
|  |       user.description = description; | ||||||
|  |     } | ||||||
|  |     await user.save(); | ||||||
|  |     ctx.body = await user.getInfo(); | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
|  | app | ||||||
|  |   .route({ | ||||||
|  |     path: 'user', | ||||||
|  |     key: 'switchOrg', | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const tokenUser = ctx.state.tokenUser; | ||||||
|  |     const { username, type = 'org' } = ctx.query.data || {}; | ||||||
|  |     if (!username && type === 'org') { | ||||||
|  |       throw new CustomError('username is required'); | ||||||
|  |     } | ||||||
|  |     let me: User; | ||||||
|  |     if (tokenUser.uid) { | ||||||
|  |       me = await User.findByPk(tokenUser.uid); | ||||||
|  |     } else { | ||||||
|  |       me = await User.findByPk(tokenUser.id); | ||||||
|  |     } | ||||||
|  |     if (type === 'user') { | ||||||
|  |       const token = await me.createToken(); | ||||||
|  |       ctx.body = token; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const orgUser = await User.findOne({ where: { username } }); | ||||||
|  |     if (!orgUser) { | ||||||
|  |       throw new CustomError('org not found'); | ||||||
|  |     } | ||||||
|  |     const user = await Org.findOne({ where: { username } }); | ||||||
|  |     const users = user.users; | ||||||
|  |     const index = users.findIndex((u) => u.uid === me.id); | ||||||
|  |     if (index === -1) { | ||||||
|  |       throw new CustomError('Permission denied'); | ||||||
|  |     } | ||||||
|  |     const token = await orgUser.createToken(me.id); | ||||||
|  |     ctx.body = token; | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
							
								
								
									
										98
									
								
								src/routes/user/org.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/routes/user/org.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | import { app, sequelize } from '@/app.ts'; | ||||||
|  | import { Org } from '@/models/org.ts'; | ||||||
|  | import { User } from '@/models/user.ts'; | ||||||
|  | import { CustomError } from '@abearxiong/router'; | ||||||
|  | import { Op } from 'sequelize'; | ||||||
|  |  | ||||||
|  | app | ||||||
|  |   .route({ | ||||||
|  |     path: 'org', | ||||||
|  |     key: 'list', | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const tokenUser = ctx.state.tokenUser; | ||||||
|  |     const list = await Org.findAll({ | ||||||
|  |       order: [['updatedAt', 'DESC']], | ||||||
|  |       where: { | ||||||
|  |         users: { | ||||||
|  |           [Op.contains]: [ | ||||||
|  |             { | ||||||
|  |               uid: tokenUser.id, | ||||||
|  |             }, | ||||||
|  |           ], | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     ctx.body = list; | ||||||
|  |     return ctx; | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
|  |  | ||||||
|  | app | ||||||
|  |   .route({ | ||||||
|  |     path: 'org', | ||||||
|  |     key: 'get', | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const id = ctx.query.id; | ||||||
|  |     if (!id) { | ||||||
|  |       throw new CustomError('id is required'); | ||||||
|  |     } | ||||||
|  |     ctx.body = await Org.findByPk(id); | ||||||
|  |     return ctx; | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
|  |  | ||||||
|  | app | ||||||
|  |   .route({ | ||||||
|  |     path: 'org', | ||||||
|  |     key: 'update', | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const tokenUser = ctx.state.tokenUser; | ||||||
|  |     const { username, description } = ctx.query.data; | ||||||
|  |     if (!username) { | ||||||
|  |       throw new CustomError('username is required'); | ||||||
|  |     } | ||||||
|  |     const user = await User.createOrg(username, tokenUser.id, description); | ||||||
|  |     ctx.body = { | ||||||
|  |       id: user.id, | ||||||
|  |       username: user.username, | ||||||
|  |       description: user.description, | ||||||
|  |     }; | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
|  |  | ||||||
|  | app | ||||||
|  |   .route({ | ||||||
|  |     path: 'org', | ||||||
|  |     key: 'delete', | ||||||
|  |     middleware: ['auth'], | ||||||
|  |   }) | ||||||
|  |   .define(async (ctx) => { | ||||||
|  |     const tokenUser = ctx.state.tokenUser; | ||||||
|  |     const id = ctx.query.id; | ||||||
|  |     if (!id) { | ||||||
|  |       throw new CustomError('id is required'); | ||||||
|  |     } | ||||||
|  |     const org = await Org.findByPk(id); | ||||||
|  |     if (!org) { | ||||||
|  |       throw new CustomError('org not found'); | ||||||
|  |     } | ||||||
|  |     const username = org.username; | ||||||
|  |     const users = org.users; | ||||||
|  |     const owner = users.find((u) => u.role === 'owner'); | ||||||
|  |     if (owner.uid !== tokenUser.id) { | ||||||
|  |       throw new CustomError('Permission denied'); | ||||||
|  |     } | ||||||
|  |     await org.destroy({ force: true }); | ||||||
|  |     const orgUser = await User.findOne({ | ||||||
|  |       where: { username }, | ||||||
|  |     }); | ||||||
|  |     await orgUser.destroy({ force: true }); | ||||||
|  |     ctx.body = 'success'; | ||||||
|  |   }) | ||||||
|  |   .addTo(app); | ||||||
		Reference in New Issue
	
	Block a user