Browse Source

refactor(core): Apply Orderable interface, moveToIndex fn to collections

First step is to define the interface and apply the generic ordering logic to the Collection entity. Relates to #156
Michael Bromley 6 years ago
parent
commit
75d90bb9b0

+ 8 - 1
packages/core/src/common/types/common-types.ts

@@ -17,6 +17,13 @@ export interface SoftDeletable {
     deletedAt: Date | null;
 }
 
+/**
+ * Entities which can be ordered relative to their siblings in a list.
+ */
+export interface Orderable {
+    position: number;
+}
+
 /**
  * Creates a type based on T, but with all properties non-optional
  * and readonly.
@@ -49,7 +56,7 @@ export interface ListQueryOptions<T extends VendureEntity> {
  * nullable fields have the type `field?: <type> | null`.
  */
 export type NullOptionals<T> = {
-    [K in keyof T]: undefined extends T[K] ? NullOptionals<T[K]> | null : NullOptionals<T[K]>
+    [K in keyof T]: undefined extends T[K] ? NullOptionals<T[K]> | null : NullOptionals<T[K]>;
 };
 
 export type SortOrder = 'ASC' | 'DESC';

+ 3 - 2
packages/core/src/entity/collection/collection.entity.ts

@@ -11,7 +11,7 @@ import {
     TreeParent,
 } from 'typeorm';
 
-import { ChannelAware } from '../../common/types/common-types';
+import { ChannelAware, Orderable } from '../../common/types/common-types';
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
 import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { Asset } from '../asset/asset.entity';
@@ -34,7 +34,8 @@ import { CollectionTranslation } from './collection-translation.entity';
 // Therefore we will just use an adjacency list which will have a perf impact when needing to lookup
 // decendants or ancestors more than 1 level removed.
 // @Tree('closure-table')
-export class Collection extends VendureEntity implements Translatable, HasCustomFields, ChannelAware {
+export class Collection extends VendureEntity
+    implements Translatable, HasCustomFields, ChannelAware, Orderable {
     constructor(input?: DeepPartial<Collection>) {
         super(input);
     }

+ 30 - 0
packages/core/src/service/helpers/utils/move-to-index.ts

@@ -0,0 +1,30 @@
+import { Orderable } from '../../../common/types/common-types';
+import { idsAreEqual } from '../../../common/utils';
+import { VendureEntity } from '../../../entity/base/base.entity';
+
+/**
+ * Moves the target Orderable entity to the given index amongst its siblings.
+ * Returns the siblings (including the target) which should then be
+ * persisted to the database.
+ */
+export function moveToIndex<T extends Orderable & VendureEntity>(
+    index: number,
+    target: T,
+    siblings: T[],
+): T[] {
+    const normalizedIndex = Math.max(Math.min(index, siblings.length), 0);
+    let currentIndex = siblings.findIndex(sibling => idsAreEqual(sibling.id, target.id));
+    const orderedSiblings = [...siblings].sort((a, b) => (a.position > b.position ? 1 : -1));
+    const siblingsWithTarget = currentIndex < 0 ? [...orderedSiblings, target] : [...orderedSiblings];
+    currentIndex = siblingsWithTarget.findIndex(sibling => idsAreEqual(sibling.id, target.id));
+    if (currentIndex !== normalizedIndex) {
+        siblingsWithTarget.splice(normalizedIndex, 0, siblingsWithTarget.splice(currentIndex, 1)[0]);
+        siblingsWithTarget.forEach((collection, i) => {
+            collection.position = i;
+            if (target.id === collection.id) {
+                target.position = i;
+            }
+        });
+    }
+    return siblingsWithTarget;
+}

+ 4 - 22
packages/core/src/service/services/collection.service.ts

@@ -12,7 +12,6 @@ import {
 import { pick } from '@vendure/common/lib/pick';
 import { ROOT_COLLECTION_NAME } from '@vendure/common/lib/shared-constants';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Subject } from 'rxjs';
 import { debounceTime } from 'rxjs/operators';
 import { Connection } from 'typeorm';
 
@@ -40,6 +39,7 @@ import { AssetUpdater } from '../helpers/asset-updater/asset-updater';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
+import { moveToIndex } from '../helpers/utils/move-to-index';
 import { translateDeep } from '../helpers/utils/translate-entity';
 import { ApplyCollectionFiltersMessage } from '../types/collection-messages';
 
@@ -305,36 +305,18 @@ export class CollectionService implements OnModuleInit {
             throw new IllegalOperationError(`error.cannot-move-collection-into-self`);
         }
 
-        const siblings = await this.connection
+        let siblings = await this.connection
             .getRepository(Collection)
             .createQueryBuilder('collection')
             .leftJoin('collection.parent', 'parent')
             .where('parent.id = :id', { id: input.parentId })
-            .orderBy('collection.position', 'ASC')
             .getMany();
         const normalizedIndex = Math.max(Math.min(input.index, siblings.length), 0);
 
-        if (idsAreEqual(target.parent.id, input.parentId)) {
-            const currentIndex = siblings.findIndex(cat => idsAreEqual(cat.id, input.collectionId));
-            if (currentIndex !== normalizedIndex) {
-                siblings.splice(normalizedIndex, 0, siblings.splice(currentIndex, 1)[0]);
-                siblings.forEach((collection, index) => {
-                    collection.position = index;
-                    if (target.id === collection.id) {
-                        target.position = index;
-                    }
-                });
-            }
-        } else {
+        if (!idsAreEqual(target.parent.id, input.parentId)) {
             target.parent = new Collection({ id: input.parentId });
-            siblings.splice(normalizedIndex, 0, target);
-            siblings.forEach((collection, index) => {
-                collection.position = index;
-                if (target.id === collection.id) {
-                    target.position = index;
-                }
-            });
         }
+        siblings = moveToIndex(input.index, target, siblings);
 
         await this.connection.getRepository(Collection).save(siblings);
         await this.applyCollectionFilters(ctx, [target]);