Bläddra i källkod

re-impl retrievePageRedirectEndpoints with $graphLookup

Yuki Takei 3 år sedan
förälder
incheckning
f586e068f8

+ 1 - 1
packages/app/src/pages/[[...path]].page.tsx

@@ -368,7 +368,7 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
 
   if (!isPermalink) {
     // check redirects
-    const chains = await PageRedirect.retrievePageRedirectChains(currentPathname);
+    const chains = await PageRedirect.retrievePageRedirectEndpoints(currentPathname);
     if (chains != null) {
       // overwrite currentPathname
       currentPathname = chains.end.toPath;

+ 42 - 14
packages/app/src/server/models/page-redirect.ts

@@ -4,22 +4,32 @@ import {
   Schema, Model, Document,
 } from 'mongoose';
 
+import loggerFactory from '~/utils/logger';
+
 import { getOrCreateModel } from '../util/mongoose-utils';
 
-export interface IPageRedirectChains {
-  start: IPageRedirect,
-  end: IPageRedirect,
-}
 
-export interface IPageRedirect {
+const logger = loggerFactory('growi:models:page-redirects');
+
+
+export type IPageRedirect = {
   fromPath: string,
   toPath: string,
 }
 
+const CHAINS_ATTRIBUTE_NAME = 'chains';
+type IPageRedirectChains = IPageRedirect & { [CHAINS_ATTRIBUTE_NAME]: IPageRedirect[] };
+
+export type IPageRedirectEndpoints = {
+  start: IPageRedirect,
+  end: IPageRedirect,
+}
+
+
 export interface PageRedirectDocument extends IPageRedirect, Document {}
 
 export interface PageRedirectModel extends Model<PageRedirectDocument> {
-  retrievePageRedirectChains(fromPath: string, storedChains?: IPageRedirectChains): Promise<IPageRedirectChains>
+  retrievePageRedirectEndpoints(fromPath: string): Promise<IPageRedirectEndpoints>
   removePageRedirectByToPath(toPath: string): Promise<void>
 }
 
@@ -30,18 +40,36 @@ const schema = new Schema<PageRedirectDocument, PageRedirectModel>({
   toPath: { type: String, required: true },
 });
 
-schema.statics.retrievePageRedirectChains = async function(fromPath: string, storedChains?: IPageRedirectChains): Promise<IPageRedirectChains|null> {
-  const chainedRedirect = await this.findOne({ fromPath });
+schema.statics.retrievePageRedirectEndpoints = async function(fromPath: string): Promise<IPageRedirectEndpoints|null> {
+  const chainedRedirect: IPageRedirectChains[] = await this.aggregate([
+    { $match: { fromPath } },
+    {
+      $graphLookup: {
+        from: 'pageredirects',
+        startWith: '$toPath',
+        connectFromField: 'toPath',
+        connectToField: 'fromPath',
+        as: CHAINS_ATTRIBUTE_NAME,
+      },
+    },
+  ]);
+
+  if (chainedRedirect.length === 0) {
+    return null;
+  }
 
-  if (chainedRedirect == null) {
-    return storedChains ?? null;
+  if (chainedRedirect.length > 1) {
+    logger.warn(`Although two or more PageRedirect documents starts from '${fromPath}' exists, The first one is used.`);
   }
 
-  const chains = storedChains ?? { start: chainedRedirect, end: chainedRedirect };
-  chains.end = chainedRedirect;
+  const chains = chainedRedirect[0];
+  const start = { fromPath: chains.fromPath, toPath: chains.toPath };
+  const chainsNum = chains[CHAINS_ATTRIBUTE_NAME].length;
+  const end = chainsNum === 0
+    ? start
+    : chains[CHAINS_ATTRIBUTE_NAME][chainsNum - 1];
 
-  // find the end recursively
-  return this.retrievePageRedirectChains(chainedRedirect.toPath, chains);
+  return { start, end };
 };
 
 schema.statics.removePageRedirectByToPath = async function(toPath: string): Promise<void> {

+ 38 - 12
packages/app/test/integration/models/page-redirect.test.js

@@ -14,6 +14,11 @@ describe('PageRedirect', () => {
     PageRedirect = mongoose.model('PageRedirect');
   });
 
+  beforeEach(async() => {
+    // clear collection
+    await PageRedirect.deleteMany({});
+  });
+
   describe('.removePageRedirectByToPath', () => {
     test('works fine', async() => {
       // setup:
@@ -44,20 +49,41 @@ describe('PageRedirect', () => {
     });
   });
 
-  describe('.retrievePageRedirectChains', () => {
+  describe('.retrievePageRedirectEndpoints', () => {
     test('shoud return null when data is not found', async() => {
       // setup:
       expect(await PageRedirect.findOne({ fromPath: '/path1' })).toBeNull();
 
       // when:
       // retrieve
-      const chains = await PageRedirect.retrievePageRedirectChains('/path1');
+      const endpoints = await PageRedirect.retrievePageRedirectEndpoints('/path1');
+
+      // then:
+      expect(endpoints).toBeNull();
+    });
+
+    test('shoud return IPageRedirectEnds (start and end is the same)', async() => {
+      // setup:
+      await PageRedirect.insertMany([
+        { fromPath: '/path1', toPath: '/path2' },
+      ]);
+      expect(await PageRedirect.findOne({ fromPath: '/path1' })).not.toBeNull();
+
+      // when:
+      // retrieve
+      const endpoints = await PageRedirect.retrievePageRedirectEndpoints('/path1');
 
       // then:
-      expect(chains).toBeNull();
+      expect(endpoints).not.toBeNull();
+      expect(endpoints.start).not.toBeNull();
+      expect(endpoints.start.fromPath).toEqual('/path1');
+      expect(endpoints.start.toPath).toEqual('/path2');
+      expect(endpoints.end).not.toBeNull();
+      expect(endpoints.end.fromPath).toEqual('/path1');
+      expect(endpoints.end.toPath).toEqual('/path2');
     });
 
-    test('shoud return IPageRedirectChains', async() => {
+    test('shoud return IPageRedirectEnds', async() => {
       // setup:
       await PageRedirect.insertMany([
         { fromPath: '/path1', toPath: '/path2' },
@@ -70,16 +96,16 @@ describe('PageRedirect', () => {
 
       // when:
       // retrieve
-      const chains = await PageRedirect.retrievePageRedirectChains('/path1');
+      const endpoints = await PageRedirect.retrievePageRedirectEndpoints('/path1');
 
       // then:
-      expect(chains).not.toBeNull();
-      expect(chains.start).not.toBeNull();
-      expect(chains.start.fromPath).toEqual('/path1');
-      expect(chains.start.toPath).toEqual('/path2');
-      expect(chains.end).not.toBeNull();
-      expect(chains.end.fromPath).toEqual('/path3');
-      expect(chains.end.toPath).toEqual('/path4');
+      expect(endpoints).not.toBeNull();
+      expect(endpoints.start).not.toBeNull();
+      expect(endpoints.start.fromPath).toEqual('/path1');
+      expect(endpoints.start.toPath).toEqual('/path2');
+      expect(endpoints.end).not.toBeNull();
+      expect(endpoints.end.fromPath).toEqual('/path3');
+      expect(endpoints.end.toPath).toEqual('/path4');
     });
   });