ソースを参照

impl /page-listing/info API

Yuki Takei 4 年 前
コミット
74a79de171

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

@@ -1,6 +1,6 @@
 import { Ref } from './common';
 import { IUser } from './user';
-import { IRevision } from './revision';
+import { IRevision, HasRevisionShortbody } from './revision';
 import { ITag } from './tag';
 import { HasObjectId } from './has-object-id';
 import { SubscriptionStatusType } from './subscription';
@@ -55,6 +55,8 @@ export type IPageInfo = IPageInfoCommon & {
   subscriptionStatus?: SubscriptionStatusType,
 }
 
+export type IPageInfoForList = IPageInfo & HasRevisionShortbody;
+
 export const isExistPageInfo = (pageInfo: IPageInfoCommon | IPageInfo | undefined): pageInfo is IPageInfo => {
   return pageInfo != null && !pageInfo.isEmpty;
 };

+ 4 - 0
packages/app/src/interfaces/revision.ts

@@ -14,3 +14,7 @@ export type IRevisionOnConflict = {
   createdAt: Date,
   user: IUser
 }
+
+export type HasRevisionShortbody = {
+  revisionShortBody?: string,
+}

+ 13 - 0
packages/app/src/server/models/page.ts

@@ -44,6 +44,7 @@ export interface PageModel extends Model<PageDocument> {
   [x: string]: any; // for obsolete methods
   createEmptyPagesByPaths(paths: string[], publicOnly?: boolean): Promise<void>
   getParentAndFillAncestors(path: string): Promise<PageDocument & { _id: any }>
+  findByIdsAndViewer(pageIds: string[], user, userGroups?, includeEmpty?: boolean): Promise<PageDocument[]>
   findByPathAndViewer(path: string | null, user, userGroups?, useFindOne?: boolean, includeEmpty?: boolean): Promise<PageDocument[]>
   findTargetAndAncestorsByPathOrId(pathOrId: string): Promise<TargetAndAncestorsResult>
   findChildrenByParentPathOrIdAndViewer(parentPathOrId: string, user, userGroups?): Promise<PageDocument[]>
@@ -266,6 +267,18 @@ const addViewerCondition = async(queryBuilder: PageQueryBuilder, user, userGroup
   queryBuilder.addConditionToFilteringByViewer(user, relatedUserGroups, false);
 };
 
+/*
+ * Find pages by ID and viewer.
+ */
+schema.statics.findByIdsAndViewer = async function(pageIds: string[], user, userGroups?, includeEmpty?: boolean): Promise<PageDocument[]> {
+  const baseQuery = this.find({ _id: { $in: pageIds } });
+  const queryBuilder = new PageQueryBuilder(baseQuery, includeEmpty);
+
+  await addViewerCondition(queryBuilder, user, userGroups);
+
+  return queryBuilder.query.exec();
+};
+
 /*
  * Find a page by path and viewer. Pass false to useFindOne to use findOne method.
  */

+ 51 - 3
packages/app/src/server/routes/apiv3/page-listing.ts

@@ -1,11 +1,15 @@
 import express, { Request, Router } from 'express';
 import { query, oneOf } from 'express-validator';
 
-import { PageDocument, PageModel } from '../../models/page';
+import mongoose from 'mongoose';
+
+import { PageModel } from '../../models/page';
 import ErrorV3 from '../../models/vo/error-apiv3';
 import loggerFactory from '../../../utils/logger';
 import Crowi from '../../crowi';
 import { ApiV3Response } from './interfaces/apiv3-response';
+import { IPageInfoForList, IPageInfoCommon, isExistPageInfo } from '~/interfaces/page';
+import PageService from '../../service/page';
 
 const logger = loggerFactory('growi:routes:apiv3:page-tree');
 
@@ -93,14 +97,58 @@ export default (crowi: Crowi): Router => {
     }
   });
 
+  // eslint-disable-next-line max-len
+  router.get('/info', accessTokenParser, loginRequired, validator.pageIdsRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
+    const { pageIds } = req.query;
+
+    const Page = mongoose.model('Page') as unknown as PageModel;
+    const Bookmark = crowi.model('Bookmark');
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const pageService: PageService = crowi.pageService!;
+
+    try {
+      const pages = await Page.findByIdsAndViewer(pageIds as string[], req.user, null, true);
+
+      const foundIds = pages.map(page => page._id);
+
+      const shortBodiesMap = await pageService.shortBodiesMapByPageIds(foundIds, req.user);
+      const bookmarkCountMap = await Bookmark.getPageIdToCountMap(foundIds) as Record<string, number>;
+
+      const idToPageInfoMap: Record<string, IPageInfoCommon|IPageInfoForList> = {};
+
+      for (const page of pages) {
+        // construct IPageInfoForList
+        const basicPageInfo = pageService.constructBasicPageInfo(page);
+
+        const pageInfo: IPageInfoCommon | IPageInfoForList = (!isExistPageInfo(basicPageInfo))
+          ? basicPageInfo
+          // create IPageInfoForList
+          : {
+            ...basicPageInfo,
+            bookmarkCount: bookmarkCountMap[page._id],
+            revisionShortBody: shortBodiesMap[page._id],
+          } as IPageInfoForList;
+
+        idToPageInfoMap[page._id] = pageInfo;
+      }
+
+      return res.apiv3(idToPageInfoMap);
+    }
+    catch (err) {
+      logger.error('Error occurred while fetching page informations.', err);
+      return res.apiv3Err(new ErrorV3('Error occurred while fetching page informations.'));
+    }
+  });
+
   // eslint-disable-next-line max-len
   router.get('/short-bodies', accessTokenParser, loginRequired, validator.pageIdsRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
     const { pageIds } = req.query;
 
     try {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      const shortBodiesMap = await crowi.pageService!.shortBodiesMapByPageIds(pageIds as string[], req.user);
-      return res.apiv3({ shortBodiesMap });
+      // const shortBodiesMap = await crowi.pageService!.shortBodiesMapByPageIds(pageIds as string[], req.user);
+      // return res.apiv3({ shortBodiesMap });
+      return res.apiv3();
     }
     catch (err) {
       logger.error('Error occurred while fetching shortBodiesMap.', err);

+ 10 - 3
packages/app/src/server/service/page.ts

@@ -1,5 +1,5 @@
 import { pagePathUtils } from '@growi/core';
-import mongoose, { QueryCursor } from 'mongoose';
+import mongoose, { ObjectId, QueryCursor } from 'mongoose';
 import escapeStringRegexp from 'escape-string-regexp';
 import streamToPromise from 'stream-to-promise';
 import pathlib from 'path';
@@ -13,9 +13,14 @@ import {
 } from '~/server/models/page';
 import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
 import ActivityDefine from '../util/activityDefine';
-import { IPage } from '~/interfaces/page';
+import {
+  IPage, IPageInfo, IPageInfoCommon,
+} from '~/interfaces/page';
 import { PageRedirectModel } from '../models/page-redirect';
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
+import { IUserHasId } from '~/interfaces/user';
+import { Ref } from '~/interfaces/common';
+import { HasObjectId } from '~/interfaces/has-object-id';
 
 const debug = require('debug')('growi:services:page');
 
@@ -1568,13 +1573,15 @@ class PageService {
     };
 
   }
+
+  async shortBodiesMapByPageIds(pageIds: ObjectId[] = [], user): Promise<Record<string, string | null>> {
     const Page = mongoose.model('Page');
     const MAX_LENGTH = 350;
 
     // aggregation options
     const viewerCondition = await generateGrantCondition(user, null);
     const filterByIds = {
-      _id: { $in: pageIds.map(id => new mongoose.Types.ObjectId(id)) },
+      _id: { $in: pageIds },
     };
 
     let pages;