Taichi Masuyama il y a 4 ans
Parent
commit
1f7c253ee9

+ 1 - 1
packages/app/src/interfaces/page.ts

@@ -14,7 +14,7 @@ export type IPage = {
   createdAt: Date,
   updatedAt: Date,
   seenUsers: Ref<IUser>[],
-  parent: Ref<IPage>,
+  parent: Ref<IPage> | null,
   isEmpty: boolean,
   redirectTo: string,
   grant: number,

+ 1 - 1
packages/app/src/server/interfaces/search.ts

@@ -33,7 +33,7 @@ export type Result<T> = {
 }
 
 export type MetaData = {
-  meta: { [key:string]: any }
+  meta?: { [key:string]: any }
 }
 
 export type SearchableData = {

+ 2 - 0
packages/app/src/server/models/index.js

@@ -1,7 +1,9 @@
 import Page from '~/server/models/page';
+import NamedQuery from '~/server/models/named-query';
 
 module.exports = {
   Page,
+  NamedQuery,
   // TODO GW-2746 bulk export pages
   // PageArchive: require('./page-archive'),
   PageTagRelation: require('./page-tag-relation'),

+ 48 - 0
packages/app/src/server/service/search-delegator/private-legacy-pages.ts

@@ -0,0 +1,48 @@
+import mongoose from 'mongoose';
+
+import { PageModel, PageDocument } from '~/server/models/page';
+import { SearchDelegatorName } from '~/interfaces/named-query';
+import { IPage } from '~/interfaces/page';
+import {
+  MetaData, Result, SearchableData, SearchDelegator,
+} from '../../interfaces/search';
+
+
+type Data = {
+  pages: IPage[]
+}
+
+class PrivateLegacyPagesDelegator implements SearchDelegator<Data> {
+
+  name!: SearchDelegatorName.PRIVATE_LEGACY_PAGES
+
+  async search(data: SearchableData | null, user, userGroups, option): Promise<Result<Data> & MetaData> {
+    const { offset, limit } = option;
+
+    if (offset == null || limit == null) {
+      throw Error('PrivateLegacyPagesDelegator requires pagination options (offset, limit).');
+    }
+
+    // find private legacy pages
+    const Page = mongoose.model('Page') as PageModel;
+    const { PageQueryBuilder } = Page;
+
+    const queryBuilder = new PageQueryBuilder(Page.find({ parent: null }));
+
+    const pages: PageDocument[] = await queryBuilder
+      .addConditionToFilteringByViewer(user, userGroups)
+      .addConditionToPagenate(offset, limit)
+      .query
+      .lean()
+      .exec();
+
+    return {
+      data: {
+        pages,
+      },
+    };
+  }
+
+}
+
+export default PrivateLegacyPagesDelegator;

+ 13 - 3
packages/app/src/server/service/search.ts

@@ -1,12 +1,14 @@
 import mongoose from 'mongoose';
 import RE2 from 're2';
 
-import { NamedQueryModel } from '../models/named-query';
 import { SearchDelegatorName } from '~/interfaces/named-query';
+
+import { NamedQueryModel } from '../models/named-query';
 import {
   SearchDelegator, SearchQueryParser, SearchResolver, ParsedQuery, Result, MetaData, SearchableData, QueryTerms,
 } from '../interfaces/search';
 import ElasticsearchDelegator from './search-delegator/elasticsearch';
+import PrivateLegacyPagesDelegator from './search-delegator/private-legacy-pages';
 
 import loggerFactory from '~/utils/logger';
 
@@ -32,7 +34,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
 
   delegator: any & SearchDelegator
 
-  nqDelegators: {[key in SearchDelegatorName]: SearchDelegator} // TODO: initialize
+  nqDelegators: {[key in SearchDelegatorName]: SearchDelegator}
 
   constructor(crowi) {
     this.crowi = crowi;
@@ -43,6 +45,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
 
     try {
       this.delegator = this.generateDelegator();
+      this.nqDelegators = this.generateNQDelegators(this.delegator);
     }
     catch (err) {
       logger.error(err);
@@ -78,6 +81,13 @@ class SearchService implements SearchQueryParser, SearchResolver {
     logger.info('No elasticsearch URI is specified so that full text search is disabled.');
   }
 
+  generateNQDelegators(defaultDelegator: SearchDelegator): {[key in SearchDelegatorName]: SearchDelegator} {
+    return {
+      [SearchDelegatorName.DEFAULT]: defaultDelegator,
+      [SearchDelegatorName.PRIVATE_LEGACY_PAGES]: new PrivateLegacyPagesDelegator(),
+    };
+  }
+
   registerUpdateEvent() {
     const pageEvent = this.crowi.event('page');
     pageEvent.on('create', this.delegator.syncPageUpdated.bind(this.delegator));
@@ -205,7 +215,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
       queryString,
       terms,
     };
-    return [this.delegator, data];
+    return [this.nqDelegators[SearchDelegatorName.DEFAULT], data];
   }
 
   async searchKeyword(keyword: string, user, userGroups, searchOpts): Promise<Result<any> & MetaData> {

+ 79 - 16
packages/app/src/test/integration/service/search/search-service.test.js

@@ -1,3 +1,5 @@
+import mongoose from 'mongoose';
+
 import SearchService from '~/server/service/search';
 import NamedQuery from '~/server/models/named-query';
 
@@ -8,11 +10,11 @@ describe('SearchService test', () => {
   let searchService;
 
   const DEFAULT = 'FullTextSearch';
+  const PRIVATE_LEGACY_PAGES = 'PrivateLegacyPages';
 
   // let NamedQuery;
 
   let dummyAliasOf;
-  let dummyDelegatorName;
 
   let namedQuery1;
   let namedQuery2;
@@ -29,6 +31,16 @@ describe('SearchService test', () => {
     searchService.nqDelegators = {
       FullTextSearch: dummyDelegator,
     };
+
+    dummyAliasOf = 'match -notmatch "phrase" -"notphrase" prefix:/pre1 -prefix:/pre2 tag:Tag1 -tag:Tag2';
+
+    await NamedQuery.insertMany([
+      { name: 'named_query1', delegatorName: PRIVATE_LEGACY_PAGES },
+      { name: 'named_query2', aliasOf: dummyAliasOf },
+    ]);
+
+    namedQuery1 = await NamedQuery.findOne({ name: 'named_query1' });
+    namedQuery2 = await NamedQuery.findOne({ name: 'named_query2' });
   });
 
 
@@ -53,18 +65,6 @@ describe('SearchService test', () => {
   });
 
   describe('parseSearchQuery()', () => {
-    beforeAll(async() => {
-      dummyDelegatorName = DEFAULT;
-      dummyAliasOf = 'match -notmatch "phrase" -"notphrase" prefix:/pre1 -prefix:/pre2 tag:Tag1 -tag:Tag2';
-
-      await NamedQuery.insertMany([
-        { name: 'named_query1', delegatorName: dummyDelegatorName },
-        { name: 'named_query2', aliasOf: dummyAliasOf },
-      ]);
-
-      namedQuery1 = await NamedQuery.findOne({ name: 'named_query1' });
-      namedQuery2 = await NamedQuery.findOne({ name: 'named_query2' });
-    });
 
     test('should return result with delegatorName', async() => {
       const queryString = '[nq:named_query1]';
@@ -72,7 +72,7 @@ describe('SearchService test', () => {
 
       const expected = {
         queryString,
-        delegatorName: dummyDelegatorName,
+        delegatorName: PRIVATE_LEGACY_PAGES,
       };
 
       expect(parsedQuery).toStrictEqual(expected);
@@ -97,7 +97,9 @@ describe('SearchService test', () => {
 
       expect(parsedQuery).toStrictEqual(expected);
     });
+  });
 
+  describe('resolve()', () => {
     test('should resolve as full-text search delegator', async() => {
       const parsedQuery = {
         queryString: dummyAliasOf,
@@ -118,14 +120,14 @@ describe('SearchService test', () => {
       const expectedData = parsedQuery;
 
       expect(data).toStrictEqual(expectedData);
-      // expect(typeof delegator.search).toBe('function'); TODO: enable test after implementing delegator initialization
+      expect(typeof delegator.search).toBe('function');
     });
 
     test('should resolve as custom search delegator', async() => {
       const queryString = '[nq:named_query1]';
       const parsedQuery = {
         queryString,
-        delegatorName: dummyDelegatorName,
+        delegatorName: PRIVATE_LEGACY_PAGES,
       };
 
       const [delegator, data] = await searchService.resolve(parsedQuery);
@@ -135,7 +137,68 @@ describe('SearchService test', () => {
       expect(data).toBe(expectedData);
       expect(typeof delegator.search).toBe('function');
     });
+  });
+
+  describe('searchKeyword()', () => {
+    test('should search with custom search delegator', async() => {
+      const Page = mongoose.model('Page');
+      const User = mongoose.model('User');
+      await User.insertMany([
+        { name: 'someone1', username: 'someone1', email: 'someone1@example.com' },
+        { name: 'someone2', username: 'someone2', email: 'someone2@example.com' },
+      ]);
 
+      const testUser1 = await User.findOne({ username: 'someone1' });
+      const testUser2 = await User.findOne({ username: 'someone2' });
+
+      await Page.insertMany([
+        {
+          path: '/user1',
+          grant: Page.GRANT_PUBLIC,
+          creator: testUser1,
+          lastUpdateUser: testUser1,
+        },
+        {
+          path: '/user1_owner',
+          grant: Page.GRANT_OWNER,
+          creator: testUser1,
+          lastUpdateUser: testUser1,
+        },
+        {
+          path: '/user2_notOwner',
+          grant: Page.GRANT_PUBLIC,
+          creator: testUser2,
+          lastUpdateUser: testUser2,
+        },
+      ]);
+
+      const page1 = await Page.findOne({ path: '/user1' });
+
+      await Page.insertMany([
+        {
+          path: '/user1/hasParent',
+          grant: Page.GRANT_PUBLIC,
+          creator: testUser1,
+          lastUpdateUser: testUser1,
+          parent: page1,
+        },
+      ]);
+
+      const queryString = '[nq:named_query1]';
+      const parsedQuery = {
+        queryString,
+        delegatorName: PRIVATE_LEGACY_PAGES,
+      };
+
+      const [delegator, data] = await searchService.resolve(parsedQuery);
+
+      const result = await delegator.search(data, testUser1, null, { limit: 10, offset: 0 });
+
+      const resultPaths = result.data.pages.map(page => page.path).sort();
+      const expectedPaths = ['/user1', '/user1_owner'].sort();
+
+      expect(resultPaths).toStrictEqual(expectedPaths);
+    });
   });
 
 });