feat: upgrade plugin-users
This commit is contained in:
		
							parent
							
								
									bf2840b089
								
							
						
					
					
						commit
						f09991d932
					
				@ -36,7 +36,7 @@ export class BelongsToField extends RelationField {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // inverse relation
 | 
			
		||||
    this.TargetModel.hasMany(collection.model);
 | 
			
		||||
    // this.TargetModel.hasMany(collection.model);
 | 
			
		||||
 | 
			
		||||
    // 建立关系之后从 pending 列表中删除
 | 
			
		||||
    database.removePendingField(this);
 | 
			
		||||
@ -76,4 +76,5 @@ export class BelongsToField extends RelationField {
 | 
			
		||||
 | 
			
		||||
export interface BelongsToFieldOptions extends BaseRelationFieldOptions, SequelizeBelongsToOptions {
 | 
			
		||||
  type: 'belongsTo';
 | 
			
		||||
  target?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -102,7 +102,7 @@ export class HasManyField extends RelationField {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // inverse relation
 | 
			
		||||
    this.TargetModel.belongsTo(collection.model);
 | 
			
		||||
    // this.TargetModel.belongsTo(collection.model);
 | 
			
		||||
 | 
			
		||||
    // 建立关系之后从 pending 列表中删除
 | 
			
		||||
    database.removePendingField(this);
 | 
			
		||||
 | 
			
		||||
@ -283,7 +283,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
 | 
			
		||||
   * @param options
 | 
			
		||||
   */
 | 
			
		||||
  @transaction()
 | 
			
		||||
  async create(options: CreateOptions): Promise<Model> {
 | 
			
		||||
  async create<M extends Model>(options: CreateOptions): Promise<M> {
 | 
			
		||||
    const transaction = await this.getTransaction(options);
 | 
			
		||||
 | 
			
		||||
    const guard = UpdateGuard.fromOptions(this.model, options);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/plugin-notifications/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/plugin-notifications/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
export { default } from './server';
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/plugin-system-settings/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/plugin-system-settings/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
export { default } from './server';
 | 
			
		||||
@ -6,7 +6,7 @@ export default {
 | 
			
		||||
  async load() {
 | 
			
		||||
    const database = this.app.db;
 | 
			
		||||
 | 
			
		||||
    database.import({
 | 
			
		||||
    await database.import({
 | 
			
		||||
      directory: path.resolve(__dirname, 'collections'),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/plugin-users/.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/plugin-users/.npmignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
node_modules
 | 
			
		||||
*.log
 | 
			
		||||
docs
 | 
			
		||||
__tests__
 | 
			
		||||
tsconfig.json
 | 
			
		||||
src
 | 
			
		||||
.fatherrc.ts
 | 
			
		||||
							
								
								
									
										18
									
								
								packages/plugin-users/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/plugin-users/package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@nocobase/plugin-users",
 | 
			
		||||
  "version": "0.6.0-alpha.0",
 | 
			
		||||
  "main": "lib/index.js",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm",
 | 
			
		||||
    "build:cjs": "tsc --project tsconfig.build.json",
 | 
			
		||||
    "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "crypto-random-string": "^3.3.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@nocobase/test": "^0.6.0-alpha.0"
 | 
			
		||||
  },
 | 
			
		||||
  "gitHead": "e7df1f93c4e23b9a666d99ee7372c02bdaec97c4"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								packages/plugin-users/src/__tests__/fields.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								packages/plugin-users/src/__tests__/fields.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
			
		||||
import Database from '@nocobase/database';
 | 
			
		||||
import { mockServer, MockServer } from '@nocobase/test';
 | 
			
		||||
 | 
			
		||||
describe('createdBy/updatedBy', () => {
 | 
			
		||||
  let api: MockServer;
 | 
			
		||||
  let db: Database;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    api = mockServer();
 | 
			
		||||
    api.plugin(require('../server').default);
 | 
			
		||||
    await api.load();
 | 
			
		||||
    db = api.db;
 | 
			
		||||
    await db.sync();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  afterEach(async () => {
 | 
			
		||||
    await db.close();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('collection definition', () => {
 | 
			
		||||
    it('case 1', async () => {
 | 
			
		||||
      const Post = db.collection({
 | 
			
		||||
        name: 'posts',
 | 
			
		||||
        createdBy: true,
 | 
			
		||||
        updatedBy: true,
 | 
			
		||||
      });
 | 
			
		||||
      expect(Post.hasField('createdBy')).toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('case 2', async () => {
 | 
			
		||||
      const Post = db.collection({
 | 
			
		||||
        name: 'posts',
 | 
			
		||||
        createdBy: true,
 | 
			
		||||
        updatedBy: true,
 | 
			
		||||
      });
 | 
			
		||||
      await db.sync();
 | 
			
		||||
      const currentUser = await db.getCollection('users').model.create();
 | 
			
		||||
      const p1 = await Post.repository.create({
 | 
			
		||||
        context: {
 | 
			
		||||
          state: {
 | 
			
		||||
            currentUser,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      const p2 = await Post.repository.findOne({
 | 
			
		||||
        appends: ['createdBy', 'updatedBy'],
 | 
			
		||||
      });
 | 
			
		||||
      expect(p2.get('createdBy')).toMatchObject(currentUser.toJSON());
 | 
			
		||||
      expect(p2.get('updatedBy')).toMatchObject(currentUser.toJSON());
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('case 3', async () => {
 | 
			
		||||
      const Post = db.collection({
 | 
			
		||||
        name: 'posts',
 | 
			
		||||
        createdBy: true,
 | 
			
		||||
        updatedBy: true,
 | 
			
		||||
      });
 | 
			
		||||
      await db.sync();
 | 
			
		||||
      const user1 = await db.getCollection('users').model.create();
 | 
			
		||||
      const user2 = await db.getCollection('users').model.create();
 | 
			
		||||
      const p1 = await Post.repository.create<any>({
 | 
			
		||||
        context: {
 | 
			
		||||
          state: {
 | 
			
		||||
            currentUser: user1,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      await Post.repository.update({
 | 
			
		||||
        values: {},
 | 
			
		||||
        filterByPk: p1.id,
 | 
			
		||||
        context: {
 | 
			
		||||
          state: {
 | 
			
		||||
            currentUser: user2,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      const p2 = await Post.repository.findOne({
 | 
			
		||||
        appends: ['createdBy', 'updatedBy'],
 | 
			
		||||
      });
 | 
			
		||||
      expect(p2.get('createdBy')).toMatchObject(user1.toJSON());
 | 
			
		||||
      expect(p2.get('updatedBy')).toMatchObject(user2.toJSON());
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										156
									
								
								packages/plugin-users/src/actions/users.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								packages/plugin-users/src/actions/users.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,156 @@
 | 
			
		||||
import { Context, Next } from '@nocobase/actions';
 | 
			
		||||
import { PasswordField } from '@nocobase/database';
 | 
			
		||||
import cryptoRandomString from 'crypto-random-string';
 | 
			
		||||
 | 
			
		||||
export async function check(ctx: Context, next: Next) {
 | 
			
		||||
  if (ctx.state.currentUser) {
 | 
			
		||||
    const user = ctx.state.currentUser.toJSON();
 | 
			
		||||
    ctx.body = user;
 | 
			
		||||
    await next();
 | 
			
		||||
  } else {
 | 
			
		||||
    ctx.throw(401, 'Unauthorized');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function signin(ctx: Context, next: Next) {
 | 
			
		||||
  const { uniqueField = 'email', values } = ctx.action.params;
 | 
			
		||||
  console.log('signin.values', values);
 | 
			
		||||
  if (!values[uniqueField]) {
 | 
			
		||||
    ctx.throw(401, '请填写邮箱账号');
 | 
			
		||||
  }
 | 
			
		||||
  const User = ctx.db.getCollection('users');
 | 
			
		||||
  const user = await User.model.scope('withPassword').findOne<any>({
 | 
			
		||||
    where: {
 | 
			
		||||
      [uniqueField]: values[uniqueField],
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  if (!user) {
 | 
			
		||||
    ctx.throw(401, '邮箱账号未注册');
 | 
			
		||||
  }
 | 
			
		||||
  const pwd = User.getField<PasswordField>('password');
 | 
			
		||||
  const isValid = await pwd.verify(values.password, user.password);
 | 
			
		||||
  if (!isValid) {
 | 
			
		||||
    ctx.throw(401, '密码错误,请您重新输入');
 | 
			
		||||
  }
 | 
			
		||||
  if (!user.token) {
 | 
			
		||||
    user.token = cryptoRandomString({ length: 20 });
 | 
			
		||||
    await user.save();
 | 
			
		||||
  }
 | 
			
		||||
  ctx.body = user.toJSON();
 | 
			
		||||
  delete ctx.body.password;
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function signout(ctx: Context, next: Next) {
 | 
			
		||||
  ctx.body = {};
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function signup(ctx: Context, next: Next) {
 | 
			
		||||
  const User = ctx.db.getCollection('users');
 | 
			
		||||
  const { values } = ctx.action.params;
 | 
			
		||||
  try {
 | 
			
		||||
    const user = await User.model.create(values);
 | 
			
		||||
    ctx.body = user;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    if (error.errors) {
 | 
			
		||||
      ctx.throw(401, error.errors.map((data) => data.message).join(', '));
 | 
			
		||||
    } else {
 | 
			
		||||
      ctx.throw(401, '注册失败');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function lostpassword(ctx: Context, next: Next) {
 | 
			
		||||
  const {
 | 
			
		||||
    values: { email },
 | 
			
		||||
  } = ctx.action.params;
 | 
			
		||||
  if (!email) {
 | 
			
		||||
    ctx.throw(401, '请填写邮箱账号');
 | 
			
		||||
  }
 | 
			
		||||
  const User = ctx.db.getCollection('users');
 | 
			
		||||
  const user = await User.model.findOne<any>({
 | 
			
		||||
    where: {
 | 
			
		||||
      email,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  if (!user) {
 | 
			
		||||
    ctx.throw(401, '邮箱账号未注册');
 | 
			
		||||
  }
 | 
			
		||||
  user.resetToken = cryptoRandomString({ length: 20 });
 | 
			
		||||
  await user.save();
 | 
			
		||||
  ctx.body = user;
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function resetpassword(ctx: Context, next: Next) {
 | 
			
		||||
  const {
 | 
			
		||||
    values: { email, password, resetToken },
 | 
			
		||||
  } = ctx.action.params;
 | 
			
		||||
  const User = ctx.db.getCollection('users');
 | 
			
		||||
  const user = await User.model.findOne<any>({
 | 
			
		||||
    where: {
 | 
			
		||||
      email,
 | 
			
		||||
      resetToken,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  if (!user) {
 | 
			
		||||
    ctx.throw(401, 'Unauthorized');
 | 
			
		||||
  }
 | 
			
		||||
  user.token = null;
 | 
			
		||||
  user.resetToken = null;
 | 
			
		||||
  user.password = password;
 | 
			
		||||
  await user.save();
 | 
			
		||||
  ctx.body = user;
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getUserByResetToken(ctx: Context, next: Next) {
 | 
			
		||||
  const { token } = ctx.action.params;
 | 
			
		||||
  const User = ctx.db.getCollection('users');
 | 
			
		||||
  const user = await User.model.findOne({
 | 
			
		||||
    where: {
 | 
			
		||||
      resetToken: token,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  if (!user) {
 | 
			
		||||
    ctx.throw(401, 'Unauthorized');
 | 
			
		||||
  }
 | 
			
		||||
  ctx.body = user;
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function updateProfile(ctx: Context, next: Next) {
 | 
			
		||||
  const { values } = ctx.action.params;
 | 
			
		||||
  if (!ctx.state.currentUser) {
 | 
			
		||||
    ctx.throw(401, 'Unauthorized');
 | 
			
		||||
  }
 | 
			
		||||
  await ctx.state.currentUser.update(values);
 | 
			
		||||
  ctx.body = ctx.state.currentUser;
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function changePassword(ctx: Context, next: Next) {
 | 
			
		||||
  const {
 | 
			
		||||
    values: { oldPassword, newPassword },
 | 
			
		||||
  } = ctx.action.params;
 | 
			
		||||
  if (!ctx.state.currentUser) {
 | 
			
		||||
    ctx.throw(401, 'Unauthorized');
 | 
			
		||||
  }
 | 
			
		||||
  const User = ctx.db.getCollection('users');
 | 
			
		||||
  const user = await User.model.scope('withPassword').findOne<any>({
 | 
			
		||||
    where: {
 | 
			
		||||
      email: ctx.state.currentUser.email,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  const pwd = User.getField<PasswordField>('password');
 | 
			
		||||
  const isValid = await pwd.verify(oldPassword, user.password);
 | 
			
		||||
  if (!isValid) {
 | 
			
		||||
    ctx.throw(401, '密码错误,请您重新输入');
 | 
			
		||||
  }
 | 
			
		||||
  user.password = newPassword;
 | 
			
		||||
  user.save();
 | 
			
		||||
  ctx.body = ctx.state.currentUser.toJSON();
 | 
			
		||||
  await next();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								packages/plugin-users/src/collections/users.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								packages/plugin-users/src/collections/users.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
import { CollectionOptions } from '@nocobase/database';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'users',
 | 
			
		||||
  title: '{{t("Users")}}',
 | 
			
		||||
  sortable: 'sort',
 | 
			
		||||
  scopes: {
 | 
			
		||||
    withPassword: {
 | 
			
		||||
      attributes: { include: ['password'] },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  defaultScope: {
 | 
			
		||||
    attributes: { exclude: ['password'] },
 | 
			
		||||
  },
 | 
			
		||||
  fields: [
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'string',
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'nickname',
 | 
			
		||||
      uiSchema: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        title: '{{t("Nickname")}}',
 | 
			
		||||
        'x-component': 'Input',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'email',
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'email',
 | 
			
		||||
      unique: true,
 | 
			
		||||
      uiSchema: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        title: '{{t("Email")}}',
 | 
			
		||||
        'x-component': 'Input',
 | 
			
		||||
        require: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'password',
 | 
			
		||||
      type: 'password',
 | 
			
		||||
      name: 'password',
 | 
			
		||||
      uiSchema: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        title: '{{t("Password")}}',
 | 
			
		||||
        'x-component': 'Password',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'linkTo',
 | 
			
		||||
      type: 'belongsToMany',
 | 
			
		||||
      name: 'roles',
 | 
			
		||||
      target: 'roles',
 | 
			
		||||
      foreignKey: 'user_id',
 | 
			
		||||
      otherKey: 'role_name',
 | 
			
		||||
      sourceKey: 'id',
 | 
			
		||||
      targetKey: 'name',
 | 
			
		||||
      uiSchema: {
 | 
			
		||||
        type: 'array',
 | 
			
		||||
        title: '{{t("Roles")}}',
 | 
			
		||||
        'x-component': 'Select.Drawer',
 | 
			
		||||
        'x-component-props': {
 | 
			
		||||
          multiple: true,
 | 
			
		||||
          fieldNames: {
 | 
			
		||||
            label: 'title',
 | 
			
		||||
            value: 'name',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        'x-decorator': 'FormItem',
 | 
			
		||||
        'x-designable-bar': 'Select.Drawer.DesignableBar',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'select',
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'appLang',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'token',
 | 
			
		||||
      unique: true,
 | 
			
		||||
      hidden: true,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'resetToken',
 | 
			
		||||
      unique: true,
 | 
			
		||||
      hidden: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
} as CollectionOptions;
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/plugin-users/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/plugin-users/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
export { default } from './server';
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/plugin-users/src/middlewares/check.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/plugin-users/src/middlewares/check.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
// TODO(usage): 拦截用户的处理暂时作为一个中间件导出,应用需要的时候可以直接使用这个中间件
 | 
			
		||||
export function check(options) {
 | 
			
		||||
  return async function check(ctx, next) {
 | 
			
		||||
    const { currentUser } = ctx.state;
 | 
			
		||||
    if (!currentUser) {
 | 
			
		||||
      return ctx.throw(401, 'Unauthorized');
 | 
			
		||||
    }
 | 
			
		||||
    return next();
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								packages/plugin-users/src/middlewares/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								packages/plugin-users/src/middlewares/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
export { check } from './check';
 | 
			
		||||
export { parseToken } from './parseToken';
 | 
			
		||||
							
								
								
									
										23
									
								
								packages/plugin-users/src/middlewares/parseToken.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/plugin-users/src/middlewares/parseToken.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
import { Context, Next } from '@nocobase/actions';
 | 
			
		||||
 | 
			
		||||
// TODO(feature): 表名应在 options 中配置
 | 
			
		||||
// 中间件默认只解决解析 token 和附加对应 user 的工作,不解决是否提前报 401 退出。
 | 
			
		||||
 | 
			
		||||
// 因为是否提供匿名访问资源是应用决定的,不是使用插件就一定不能匿名访问。
 | 
			
		||||
export function parseToken(options) {
 | 
			
		||||
  return async function parseToken(ctx: Context, next: Next) {
 | 
			
		||||
    const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, '');
 | 
			
		||||
    const User = ctx.db.getCollection('users');
 | 
			
		||||
    const user = await User.model.findOne({
 | 
			
		||||
      where: {
 | 
			
		||||
        token,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (user) {
 | 
			
		||||
      ctx.state.currentUser = user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return next();
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								packages/plugin-users/src/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								packages/plugin-users/src/server.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import * as actions from './actions/users';
 | 
			
		||||
import * as middlewares from './middlewares';
 | 
			
		||||
import { Collection } from '@nocobase/database';
 | 
			
		||||
import { PluginOptions } from '@nocobase/server';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'users',
 | 
			
		||||
  async load() {
 | 
			
		||||
    const database = this.app.db;
 | 
			
		||||
    const resourcer = this.app.resourcer;
 | 
			
		||||
 | 
			
		||||
    this.app.on('db.init', async () => {
 | 
			
		||||
      const User = database.getCollection('users');
 | 
			
		||||
      await User.model.create({
 | 
			
		||||
        nickname: 'Super Admin',
 | 
			
		||||
        email: process.env.ADMIN_EMAIL || 'admin@nocobase.com',
 | 
			
		||||
        password: process.env.ADMIN_PASSWORD || 'admin123',
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    database.on('afterDefineCollection', (collection: Collection) => {
 | 
			
		||||
      let { createdBy, updatedBy } = collection.options;
 | 
			
		||||
      if (createdBy === true) {
 | 
			
		||||
        collection.setField('createdById', {
 | 
			
		||||
          type: 'context',
 | 
			
		||||
          dataType: 'integer',
 | 
			
		||||
          dataIndex: 'state.currentUser.id',
 | 
			
		||||
          createOnly: true,
 | 
			
		||||
        });
 | 
			
		||||
        collection.setField('createdBy', {
 | 
			
		||||
          type: 'belongsTo',
 | 
			
		||||
          target: 'users',
 | 
			
		||||
          foreignKey: 'createdById',
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      if (updatedBy === true) {
 | 
			
		||||
        collection.setField('updatedById', {
 | 
			
		||||
          type: 'context',
 | 
			
		||||
          dataType: 'integer',
 | 
			
		||||
          dataIndex: 'state.currentUser.id',
 | 
			
		||||
        });
 | 
			
		||||
        collection.setField('updatedBy', {
 | 
			
		||||
          type: 'belongsTo',
 | 
			
		||||
          target: 'users',
 | 
			
		||||
          foreignKey: 'updatedById',
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await database.import({
 | 
			
		||||
      directory: path.resolve(__dirname, 'collections'),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    for (const [key, action] of Object.entries(actions)) {
 | 
			
		||||
      resourcer.registerActionHandler(`users:${key}`, action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resourcer.use(middlewares.parseToken({}));
 | 
			
		||||
  },
 | 
			
		||||
} as PluginOptions;
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/plugin-users/tsconfig.build.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/plugin-users/tsconfig.build.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "../../tsconfig.build.json",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "outDir": "./lib",
 | 
			
		||||
    "declaration": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["./src/**/*.ts", "./src/**/*.tsx"],
 | 
			
		||||
  "exclude": ["./src/__tests__/*", "./esm/*", "./lib/*"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								packages/plugin-users/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/plugin-users/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "../../tsconfig.json",
 | 
			
		||||
  "include": ["./src/**/*.ts", "./src/**/*.tsx"],
 | 
			
		||||
  "exclude": ["./esm/*", "./lib/*"]
 | 
			
		||||
}
 | 
			
		||||
@ -4647,6 +4647,13 @@ crypto-random-string@^1.0.0:
 | 
			
		||||
  resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
 | 
			
		||||
  integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
 | 
			
		||||
 | 
			
		||||
crypto-random-string@^3.3.0:
 | 
			
		||||
  version "3.3.1"
 | 
			
		||||
  resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.3.1.tgz#13cee94cac8001e4842501608ef779e0ed08f82d"
 | 
			
		||||
  integrity sha512-5j88ECEn6h17UePrLi6pn1JcLtAiANa3KExyr9y9Z5vo2mv56Gh3I4Aja/B9P9uyMwyxNHAHWv+nE72f30T5Dg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    type-fest "^0.8.1"
 | 
			
		||||
 | 
			
		||||
css-blank-pseudo@^0.1.4:
 | 
			
		||||
  version "0.1.4"
 | 
			
		||||
  resolved "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user