refactor: change sort strategy from offset to targetId (#37)
* refactor: change sort strategy from offset to targetId * fix: remove unnecessary query to optimize performance
This commit is contained in:
		
							parent
							
								
									841249f58c
								
							
						
					
					
						commit
						d1372e273a
					
				@ -85,6 +85,11 @@ resourcer.define({
 | 
				
			|||||||
  name: 'posts.comments',
 | 
					  name: 'posts.comments',
 | 
				
			||||||
  actions: actions.associate,
 | 
					  actions: actions.associate,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					resourcer.define({
 | 
				
			||||||
 | 
					  type: 'hasMany',
 | 
				
			||||||
 | 
					  name: 'users.posts',
 | 
				
			||||||
 | 
					  actions: actions.associate,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
resourcer.define({
 | 
					resourcer.define({
 | 
				
			||||||
  type: 'belongsTo',
 | 
					  type: 'belongsTo',
 | 
				
			||||||
  name: 'posts.user',
 | 
					  name: 'posts.user',
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,7 @@ describe('get', () => {
 | 
				
			|||||||
  
 | 
					  
 | 
				
			||||||
  afterAll(() => db.close());
 | 
					  afterAll(() => db.close());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe.only('sort value initialization', () => {
 | 
					  describe('sort value initialization', () => {
 | 
				
			||||||
    it('initialization by bulkCreate', async () => {
 | 
					    it('initialization by bulkCreate', async () => {
 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					      const Post = db.getModel('posts');
 | 
				
			||||||
      const posts = await Post.findAll({
 | 
					      const posts = await Post.findAll({
 | 
				
			||||||
@ -85,188 +85,165 @@ describe('get', () => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('sort in whole table', () => {
 | 
					  describe('sort in whole table', () => {
 | 
				
			||||||
    it('init sort value', async () => {
 | 
					    it('move id=1 to position at id=2', async () => {
 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('move id=1 by offset=1', async () => {
 | 
					 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					 | 
				
			||||||
      await agent
 | 
					      await agent
 | 
				
			||||||
        .post('/posts:sort/1')
 | 
					        .post('/posts:sort/1')
 | 
				
			||||||
        .send({
 | 
					        .send({
 | 
				
			||||||
          offset: 1,
 | 
					          field: 'sort',
 | 
				
			||||||
 | 
					          targetId: 2,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const post1 = await Post.findByPk(1);
 | 
					      const Post = db.getModel('posts');
 | 
				
			||||||
      expect(post1.get('sort')).toBe(1);
 | 
					      const posts = await Post.findAll({
 | 
				
			||||||
 | 
					        attributes: ['id', 'sort'],
 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					        order: [['id', 'ASC']]
 | 
				
			||||||
      expect(post2.get('sort')).toBe(0);
 | 
					      });
 | 
				
			||||||
 | 
					      expect(posts.map(item => item.get())).toEqual([
 | 
				
			||||||
 | 
					        { id: 1, sort: 2 },
 | 
				
			||||||
 | 
					        { id: 2, sort: 1 },
 | 
				
			||||||
 | 
					        { id: 3, sort: 3 },
 | 
				
			||||||
 | 
					        { id: 4, sort: 4 },
 | 
				
			||||||
 | 
					        { id: 5, sort: 5 },
 | 
				
			||||||
 | 
					        { id: 6, sort: 6 },
 | 
				
			||||||
 | 
					        { id: 7, sort: 7 },
 | 
				
			||||||
 | 
					        { id: 8, sort: 8 },
 | 
				
			||||||
 | 
					        { id: 9, sort: 9 },
 | 
				
			||||||
 | 
					        { id: 10, sort: 10 }
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('move id=1 by offset=9', async () => {
 | 
					    it('move id=2 to position at id=1', async () => {
 | 
				
			||||||
 | 
					      await agent
 | 
				
			||||||
 | 
					        .post('/posts:sort/2')
 | 
				
			||||||
 | 
					        .send({
 | 
				
			||||||
 | 
					          field: 'sort',
 | 
				
			||||||
 | 
					          targetId: 1,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					      const Post = db.getModel('posts');
 | 
				
			||||||
 | 
					      const posts = await Post.findAll({
 | 
				
			||||||
 | 
					        attributes: ['id', 'sort'],
 | 
				
			||||||
 | 
					        order: [['id', 'ASC']]
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(posts.map(item => item.get())).toEqual([
 | 
				
			||||||
 | 
					        { id: 1, sort: 2 },
 | 
				
			||||||
 | 
					        { id: 2, sort: 1 },
 | 
				
			||||||
 | 
					        { id: 3, sort: 3 },
 | 
				
			||||||
 | 
					        { id: 4, sort: 4 },
 | 
				
			||||||
 | 
					        { id: 5, sort: 5 },
 | 
				
			||||||
 | 
					        { id: 6, sort: 6 },
 | 
				
			||||||
 | 
					        { id: 7, sort: 7 },
 | 
				
			||||||
 | 
					        { id: 8, sort: 8 },
 | 
				
			||||||
 | 
					        { id: 9, sort: 9 },
 | 
				
			||||||
 | 
					        { id: 10, sort: 10 }
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('move id=1 to position at id=10', async () => {
 | 
				
			||||||
      await agent
 | 
					      await agent
 | 
				
			||||||
        .post('/posts:sort/1')
 | 
					        .post('/posts:sort/1')
 | 
				
			||||||
        .send({
 | 
					        .send({
 | 
				
			||||||
          offset: 9,
 | 
					          field: 'sort',
 | 
				
			||||||
 | 
					          targetId: 10,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const post1 = await Post.findByPk(1);
 | 
					 | 
				
			||||||
      expect(post1.get('sort')).toBe(9);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					 | 
				
			||||||
      expect(post2.get('sort')).toBe(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post10 = await Post.findByPk(10);
 | 
					 | 
				
			||||||
      expect(post10.get('sort')).toBe(8);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post11 = await Post.findByPk(11);
 | 
					 | 
				
			||||||
      expect(post11.get('sort')).toBe(10);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('move id=1 by offset=-1', async () => {
 | 
					 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					      const Post = db.getModel('posts');
 | 
				
			||||||
      await agent
 | 
					      const posts = await Post.findAll({
 | 
				
			||||||
        .post('/posts:sort/1')
 | 
					        attributes: ['id', 'sort'],
 | 
				
			||||||
        .send({
 | 
					        order: [['id', 'ASC']]
 | 
				
			||||||
          offset: -1,
 | 
					      });
 | 
				
			||||||
        });
 | 
					      expect(posts.map(item => item.get())).toEqual([
 | 
				
			||||||
 | 
					        { id: 1, sort: 10 },
 | 
				
			||||||
      const post1 = await Post.findByPk(1);
 | 
					        { id: 2, sort: 1 },
 | 
				
			||||||
      expect(post1.get('sort')).toBe(0);
 | 
					        { id: 3, sort: 2 },
 | 
				
			||||||
 | 
					        { id: 4, sort: 3 },
 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					        { id: 5, sort: 4 },
 | 
				
			||||||
      expect(post2.get('sort')).toBe(1);
 | 
					        { id: 6, sort: 5 },
 | 
				
			||||||
    });
 | 
					        { id: 7, sort: 6 },
 | 
				
			||||||
 | 
					        { id: 8, sort: 7 },
 | 
				
			||||||
    it('move id=2 by offset=8', async () => {
 | 
					        { id: 9, sort: 8 },
 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					        { id: 10, sort: 9 }
 | 
				
			||||||
      await agent
 | 
					      ]);
 | 
				
			||||||
        .post('/posts:sort/2')
 | 
					 | 
				
			||||||
        .send({
 | 
					 | 
				
			||||||
          offset: 8,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					 | 
				
			||||||
      expect(post2.get('sort')).toBe(9);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post10 = await Post.findByPk(10);
 | 
					 | 
				
			||||||
      expect(post10.get('sort')).toBe(8);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('move id=2 by offset=-1', async () => {
 | 
					 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					 | 
				
			||||||
      await agent
 | 
					 | 
				
			||||||
        .post('/posts:sort/2')
 | 
					 | 
				
			||||||
        .send({
 | 
					 | 
				
			||||||
          offset: -1,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					 | 
				
			||||||
      expect(post2.get('sort')).toBe(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post1 = await Post.findByPk(1);
 | 
					 | 
				
			||||||
      expect(post1.get('sort')).toBe(1);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('move id=2 by offset=Infinity', async () => {
 | 
					 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					 | 
				
			||||||
      await agent
 | 
					 | 
				
			||||||
        .post('/posts:sort/2')
 | 
					 | 
				
			||||||
        .send({
 | 
					 | 
				
			||||||
          offset: 'Infinity',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post1 = await Post.findByPk(1);
 | 
					 | 
				
			||||||
      expect(post1.get('sort')).toBe(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					 | 
				
			||||||
      expect(post2.get('sort')).toBe(10);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post10 = await Post.findByPk(10);
 | 
					 | 
				
			||||||
      expect(post10.get('sort')).toBe(9);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('move id=2 by offset=-Infinity', async () => {
 | 
					 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					 | 
				
			||||||
      await agent
 | 
					 | 
				
			||||||
        .post('/posts:sort/2')
 | 
					 | 
				
			||||||
        .send({
 | 
					 | 
				
			||||||
          offset: '-Infinity',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post1 = await Post.findByPk(1);
 | 
					 | 
				
			||||||
      expect(post1.get('sort')).toBe(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					 | 
				
			||||||
      expect(post2.get('sort')).toBe(-1);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('sort in filtered scope', () => {
 | 
					  describe('sort in filtered scope', () => {
 | 
				
			||||||
    it('move id=1 by offset=3 in scope filter[status]=publish', async () => {
 | 
					    it('move id=2 to position at id=8 (same scope value)', async () => {
 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        await agent
 | 
					 | 
				
			||||||
          .post('/posts:sort/1?filter[status]=publish')
 | 
					 | 
				
			||||||
          .send({
 | 
					 | 
				
			||||||
            offset: 3,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
      } catch (error) {
 | 
					 | 
				
			||||||
        expect(error).toBeDefined();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 在 scope 中的排序无所谓值是否与其他不在 scope 中的重复。
 | 
					 | 
				
			||||||
    it('move id=2 by offset=3 in scope filter[status]=publish', async () => {
 | 
					 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					 | 
				
			||||||
      await agent
 | 
					      await agent
 | 
				
			||||||
        .post('/posts:sort/2?filter[status]=publish')
 | 
					        .post('/posts:sort/2')
 | 
				
			||||||
        .send({
 | 
					        .send({
 | 
				
			||||||
          offset: 3,
 | 
					          field: 'sort_in_status',
 | 
				
			||||||
 | 
					          targetId: 8,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      
 | 
					
 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					      const Post = db.getModel('posts');
 | 
				
			||||||
      expect(post2.get('sort')).toBe(7);
 | 
					      const posts = await Post.findAll({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          status: 'publish'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        attributes: ['id', 'sort_in_status'],
 | 
				
			||||||
 | 
					        order: [['id', 'ASC']]
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(posts.map(item => item.get())).toEqual([
 | 
				
			||||||
 | 
					        { id: 2, sort_in_status: 4 },
 | 
				
			||||||
 | 
					        { id: 4, sort_in_status: 1 },
 | 
				
			||||||
 | 
					        { id: 6, sort_in_status: 2 },
 | 
				
			||||||
 | 
					        { id: 8, sort_in_status: 3 },
 | 
				
			||||||
 | 
					        { id: 10, sort_in_status: 5 }
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('move id=2 by offset=Infinity in scope filter[status]=publish', async () => {
 | 
					    it('move id=1 to position at id=8 (different scope value)', async () => {
 | 
				
			||||||
      await agent
 | 
					      await agent
 | 
				
			||||||
        .post('/posts:sort/2?filter[status]=publish')
 | 
					        .post('/posts:sort/1')
 | 
				
			||||||
        .send({
 | 
					        .send({
 | 
				
			||||||
          offset: 'Infinity',
 | 
					          field: 'sort_in_status',
 | 
				
			||||||
 | 
					          targetId: 8,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      const Post = db.getModel('posts');
 | 
					      const Post = db.getModel('posts');
 | 
				
			||||||
      const post2 = await Post.findByPk(2);
 | 
					      const posts = await Post.findAll({
 | 
				
			||||||
      expect(post2.get('sort')).toBe(10);
 | 
					        where: {
 | 
				
			||||||
 | 
					          status: 'publish'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        attributes: ['id', 'sort_in_status'],
 | 
				
			||||||
 | 
					        order: [['id', 'ASC']]
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(posts.map(item => item.get())).toEqual([
 | 
				
			||||||
 | 
					        { id: 1, sort_in_status: 4 },
 | 
				
			||||||
 | 
					        { id: 2, sort_in_status: 1 },
 | 
				
			||||||
 | 
					        { id: 4, sort_in_status: 2 },
 | 
				
			||||||
 | 
					        { id: 6, sort_in_status: 3 },
 | 
				
			||||||
 | 
					        { id: 8, sort_in_status: 5 },
 | 
				
			||||||
 | 
					        { id: 10, sort_in_status: 6 }
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('associations', () => {
 | 
					  describe('associations', () => {
 | 
				
			||||||
    describe('hasMany', () => {
 | 
					    describe('hasMany', () => {
 | 
				
			||||||
      it('sort only 1 item in group will never change', async () => {
 | 
					      it('move id=1 to position at id=3 (different scope value)', async () => {
 | 
				
			||||||
        await agent
 | 
					        await agent
 | 
				
			||||||
          .post('/posts/2/comments:sort/1')
 | 
					          .post('/users/1/posts:sort/1')
 | 
				
			||||||
          .send({
 | 
					          .send({
 | 
				
			||||||
            offset: 1
 | 
					            field: 'sort_in_user',
 | 
				
			||||||
 | 
					            targetId: 3,
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        const Comment = db.getModel('comments');
 | 
					 | 
				
			||||||
        const comment1 = await Comment.findByPk(1);
 | 
					 | 
				
			||||||
        expect(comment1.get('sort')).toBe(0);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it('/posts/5/comments:sort/7', async () => {
 | 
					        const Post = db.getModel('posts');
 | 
				
			||||||
        await agent
 | 
					        const posts = await Post.findAll({
 | 
				
			||||||
          .post('/posts/5/comments:sort/7')
 | 
					          where: {
 | 
				
			||||||
          .send({
 | 
					            user_id: 3
 | 
				
			||||||
            offset: 1
 | 
					          },
 | 
				
			||||||
          });
 | 
					          attributes: ['id', 'sort_in_user'],
 | 
				
			||||||
        
 | 
					          order: [['id', 'ASC']]
 | 
				
			||||||
        const Comment = db.getModel('comments');
 | 
					        });
 | 
				
			||||||
        const comment7 = await Comment.findByPk(7);
 | 
					
 | 
				
			||||||
        expect(comment7.get('sort')).toBe(1);
 | 
					        expect(posts.map(item => item.get())).toEqual([
 | 
				
			||||||
 | 
					          { id: 1, sort_in_user: 1 },
 | 
				
			||||||
 | 
					          { id: 3, sort_in_user: 2 },
 | 
				
			||||||
 | 
					          { id: 10, sort_in_user: 3 },
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import { Utils, Op } from 'sequelize';
 | 
				
			||||||
 | 
					import _ from 'lodash';
 | 
				
			||||||
import { Context, Next } from '.';
 | 
					import { Context, Next } from '.';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Model, 
 | 
					  Model, 
 | 
				
			||||||
@ -5,10 +7,9 @@ import {
 | 
				
			|||||||
  HASMANY,
 | 
					  HASMANY,
 | 
				
			||||||
  BELONGSTO,
 | 
					  BELONGSTO,
 | 
				
			||||||
  BELONGSTOMANY,
 | 
					  BELONGSTOMANY,
 | 
				
			||||||
 | 
					  whereCompare
 | 
				
			||||||
} from '@nocobase/database';
 | 
					} from '@nocobase/database';
 | 
				
			||||||
import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '@nocobase/resourcer';
 | 
					import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '@nocobase/resourcer';
 | 
				
			||||||
import { Utils, Op } from 'sequelize';
 | 
					 | 
				
			||||||
import _ from 'lodash';
 | 
					 | 
				
			||||||
import { filterByFields } from '../utils';
 | 
					import { filterByFields } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -373,7 +374,6 @@ export async function sort(ctx: Context, next: Next) {
 | 
				
			|||||||
    associatedName,
 | 
					    associatedName,
 | 
				
			||||||
    associatedKey,
 | 
					    associatedKey,
 | 
				
			||||||
    associated,
 | 
					    associated,
 | 
				
			||||||
    filter = {},
 | 
					 | 
				
			||||||
    values
 | 
					    values
 | 
				
			||||||
  } = ctx.action.params;
 | 
					  } = ctx.action.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -390,114 +390,94 @@ export async function sort(ctx: Context, next: Next) {
 | 
				
			|||||||
  const Model = ctx.db.getModel(resourceName);
 | 
					  const Model = ctx.db.getModel(resourceName);
 | 
				
			||||||
  const table = ctx.db.getTable(resourceName);
 | 
					  const table = ctx.db.getTable(resourceName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!values.offset) {
 | 
					  const { field, targetId } = values;
 | 
				
			||||||
 | 
					  if (!values.field || typeof targetId === 'undefined') {
 | 
				
			||||||
    return next();
 | 
					    return next();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  const [primaryField] = Model.primaryKeyAttributes;
 | 
					  const sortField = table.getField(field);
 | 
				
			||||||
  const sortField = values.field || table.getOptions().sortField || 'sort';
 | 
					  if (!sortField) {
 | 
				
			||||||
 | 
					    return next();
 | 
				
			||||||
  // offset 的有效值为:整型 | 'Infinity' | '-Infinity'
 | 
					  }
 | 
				
			||||||
  const offset = Number(values.offset);
 | 
					  const { primaryKeyAttribute } = Model;
 | 
				
			||||||
  const sign = offset < 0 ? {
 | 
					  const { name: sortAttr, scope = [] } = sortField.options;
 | 
				
			||||||
    op: Op.lte,
 | 
					 | 
				
			||||||
    order: 'DESC',
 | 
					 | 
				
			||||||
    direction: 1,
 | 
					 | 
				
			||||||
    extremum: 'min'
 | 
					 | 
				
			||||||
  } : {
 | 
					 | 
				
			||||||
    op: Op.gte,
 | 
					 | 
				
			||||||
    order: 'ASC',
 | 
					 | 
				
			||||||
    direction: -1,
 | 
					 | 
				
			||||||
    extremum: 'max'
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const transaction = await ctx.db.sequelize.transaction();
 | 
					  const transaction = await ctx.db.sequelize.transaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { where = {} } = Model.parseApiJson({ filter });
 | 
					  const where = {};
 | 
				
			||||||
  if (associated && resourceField instanceof HASMANY) {
 | 
					  if (associated && resourceField instanceof HASMANY) {
 | 
				
			||||||
    where[resourceField.options.foreignKey] = associatedKey;
 | 
					    where[resourceField.options.foreignKey] = associatedKey;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 找到操作对象
 | 
					  // 找到操作对象
 | 
				
			||||||
  const operand = await Model.findOne({
 | 
					  const source = await Model.findOne({
 | 
				
			||||||
    // 这里增加 where 条件是要求如果有 filter 条件,就应该在同条件的组中排序,不是同条件组的报错处理。
 | 
					 | 
				
			||||||
    where: {
 | 
					    where: {
 | 
				
			||||||
      ...where,
 | 
					      ...where,
 | 
				
			||||||
      [primaryField]: resourceKey
 | 
					      [primaryKeyAttribute]: resourceKey
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    transaction
 | 
					    transaction
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  if (!source) {
 | 
				
			||||||
  if (!operand) {
 | 
					 | 
				
			||||||
    await transaction.rollback();
 | 
					    await transaction.rollback();
 | 
				
			||||||
    // TODO: 错误需要后面统一处理
 | 
					    throw new Error(`resource(${resourceKey}) does not exist`);
 | 
				
			||||||
    throw new Error(`resource(${resourceKey}) with filter does not exist`);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let target;
 | 
					  const target = await Model.findByPk(targetId, { transaction });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 如果是有限的变动值
 | 
					  if (!target) {
 | 
				
			||||||
  if (Number.isFinite(offset)) {
 | 
					    await transaction.rollback();
 | 
				
			||||||
    const absChange = Math.abs(offset);
 | 
					    throw new Error(`resource(${targetId}) does not exist`);
 | 
				
			||||||
    const group = await Model.findAll({
 | 
					  }
 | 
				
			||||||
      where: {
 | 
					
 | 
				
			||||||
        ...where,
 | 
					  const sourceScopeWhere = source.getScopeWhere(scope);
 | 
				
			||||||
        [primaryField]: {
 | 
					  const targetScopeWhere = target.getScopeWhere(scope);
 | 
				
			||||||
          [Op.ne]: resourceKey
 | 
					  const sameScope = whereCompare(sourceScopeWhere, targetScopeWhere);
 | 
				
			||||||
        },
 | 
					
 | 
				
			||||||
        [sortField]: {
 | 
					  let increment;
 | 
				
			||||||
          [sign.op]: operand[sortField]
 | 
					  const updateWhere = { ...targetScopeWhere };
 | 
				
			||||||
        }
 | 
					  if (sameScope) {
 | 
				
			||||||
      },
 | 
					    const direction = source[sortAttr] < target[sortAttr] ? {
 | 
				
			||||||
      limit: absChange,
 | 
					      sourceOp: Op.gt,
 | 
				
			||||||
      // offset: 0,
 | 
					      targetOp: Op.lte,
 | 
				
			||||||
      attributes: [primaryField, sortField],
 | 
					      increment: -1
 | 
				
			||||||
      order: [
 | 
					    } : {
 | 
				
			||||||
        [sortField, sign.order]
 | 
					      sourceOp: Op.lt,
 | 
				
			||||||
      ],
 | 
					      targetOp: Op.gte,
 | 
				
			||||||
      transaction
 | 
					      increment: 1
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    increment = direction.increment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Object.assign(updateWhere, {
 | 
				
			||||||
 | 
					      [sortAttr]: {
 | 
				
			||||||
 | 
					        [direction.sourceOp]: source[sortAttr],
 | 
				
			||||||
 | 
					        [direction.targetOp]: target[sortAttr]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    increment = 1;
 | 
				
			||||||
 | 
					    Object.assign(updateWhere, {
 | 
				
			||||||
 | 
					      [sortAttr]: {
 | 
				
			||||||
 | 
					        [Op.gte]: target[sortAttr]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!group.length) {
 | 
					 | 
				
			||||||
      // 如果变动范围内的元素数比范围小
 | 
					 | 
				
			||||||
      // 说明全部数据不足一页
 | 
					 | 
				
			||||||
      // target = group[0][priorityKey] - sign.direction;
 | 
					 | 
				
			||||||
      // 没有元素无需变动
 | 
					 | 
				
			||||||
      await transaction.commit();
 | 
					 | 
				
			||||||
      ctx.body = operand;
 | 
					 | 
				
			||||||
      return next();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 如果变动范围内都有元素(可能出现 limit 范围内元素不足的情况)
 | 
					 | 
				
			||||||
    if (group.length === absChange) {
 | 
					 | 
				
			||||||
      target = group[group.length - 1][sortField];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await Model.increment(sortField, {
 | 
					 | 
				
			||||||
        by: sign.direction,
 | 
					 | 
				
			||||||
        where: {
 | 
					 | 
				
			||||||
          [primaryField]: {
 | 
					 | 
				
			||||||
            [Op.in]: group.map(item => item[primaryField])
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        transaction
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // 如果要求置顶或沉底(未在上一过程中计算出目标值)
 | 
					
 | 
				
			||||||
  if (typeof target === 'undefined') {
 | 
					  await Model.increment(sortAttr, {
 | 
				
			||||||
    target = await Model[sign.extremum](sortField, {
 | 
					    by: increment,
 | 
				
			||||||
      where,
 | 
					    where: updateWhere,
 | 
				
			||||||
      transaction
 | 
					    transaction
 | 
				
			||||||
    }) - sign.direction;
 | 
					  });
 | 
				
			||||||
  }
 | 
					
 | 
				
			||||||
  await operand.update({
 | 
					  await source.update({
 | 
				
			||||||
    [sortField]: target
 | 
					    [sortAttr]: target[sortAttr],
 | 
				
			||||||
 | 
					    ...targetScopeWhere
 | 
				
			||||||
  }, {
 | 
					  }, {
 | 
				
			||||||
    transaction
 | 
					    transaction
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await transaction.commit();
 | 
					  await transaction.commit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ctx.body = operand;
 | 
					  ctx.body = source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await next();
 | 
					  await next();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -259,7 +259,7 @@ export abstract class Model extends SequelizeModel {
 | 
				
			|||||||
    return data;
 | 
					    return data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getScopeWhere(scope: string[]) {
 | 
					  getScopeWhere(scope: string[] = []) {
 | 
				
			||||||
    const Model = this.constructor as ModelCtor<Model>;
 | 
					    const Model = this.constructor as ModelCtor<Model>;
 | 
				
			||||||
    const table = this.database.getTable(this.constructor.name);
 | 
					    const table = this.database.getTable(this.constructor.name);
 | 
				
			||||||
    const associations = table.getAssociations();
 | 
					    const associations = table.getAssociations();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user