Taichi Masuyama 4 лет назад
Родитель
Сommit
f1e9240e7f

+ 19 - 6
packages/app/src/server/models/page.ts

@@ -38,7 +38,7 @@ export interface PageModel extends Model<PageDocument> {
   getParentIdAndFillAncestors(path: string): Promise<string | null>
   findByPathAndViewer(path: string | null, user, userGroups?, useFindOne?): Promise<PageDocument[]>
   findSiblingsByPathAndViewer(path: string | null, user, userGroups?): Promise<PageDocument[]>
-  findAncestorsByPath(path: string): Promise<PageDocument[]>
+  findAncestorsByPathOrId(pathOrId: string): Promise<PageDocument[]>
 }
 
 const ObjectId = mongoose.Schema.Types.ObjectId;
@@ -83,14 +83,15 @@ schema.plugin(uniqueValidator);
  * Methods
  */
 const collectAncestorPaths = (path: string, ancestorPaths: string[] = []): string[] => {
-  if (isTopPage(path)) return [];
+  if (isTopPage(path)) return ancestorPaths;
 
   const parentPath = nodePath.dirname(path);
   ancestorPaths.push(parentPath);
+  return collectAncestorPaths(parentPath, ancestorPaths);
+};
 
-  if (!isTopPage(path)) return collectAncestorPaths(parentPath, ancestorPaths);
-
-  return ancestorPaths;
+const hasSlash = (str: string): boolean => {
+  return str.includes('/');
 };
 
 /*
@@ -229,7 +230,19 @@ schema.statics.findSiblingsByPathAndViewer = async function(path: string | null,
 /*
  * Find all ancestor pages by path. When duplicate pages found, it uses the oldest page as a result
  */
-schema.statics.findAncestorsByPath = async function(path: string): Promise<PageDocument[]> {
+schema.statics.findAncestorsByPathOrId = async function(pathOrId: string): Promise<PageDocument[]> {
+  let path;
+  if (!hasSlash(pathOrId)) {
+    const _id = pathOrId;
+    const page = await this.findOne({ _id });
+    if (page == null) throw Error('Page not found.');
+
+    path = page.path;
+  }
+  else {
+    path = pathOrId;
+  }
+
   const ancestorPaths = collectAncestorPaths(path);
 
   // Do not populate

+ 33 - 10
packages/app/src/server/routes/apiv3/page-listing.ts

@@ -1,8 +1,6 @@
-import express, {
-  Request, Router,
-} from 'express';
+import express, { Request, Router } from 'express';
 import { pagePathUtils } from '@growi/core';
-import { query } from 'express-validator';
+import { query, oneOf } from 'express-validator';
 
 import { PageDocument, PageModel } from '../../models/page';
 import ErrorV3 from '../../models/vo/error-apiv3';
@@ -25,10 +23,14 @@ interface AuthorizedRequest extends Request {
  * Validators
  */
 const validator = {
-  getPagesAroundTarget: [
+  pageIdAndPathRequired: [
     query('id').isMongoId().withMessage('id is required'),
     query('path').isString().withMessage('path is required'),
   ],
+  pageIdOrPathRequired: oneOf([
+    query('id').isMongoId(),
+    query('path').isString(),
+  ], 'id or path is required'),
 };
 
 /*
@@ -36,24 +38,23 @@ const validator = {
  */
 export default (crowi: Crowi): Router => {
   const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
-  // Do not use loginRequired with isGuestAllowed true since page tree may show private pages' title
+  // Do not use loginRequired with isGuestAllowed true since page tree may show private page titles
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
+  const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
 
   const router = express.Router();
 
 
   // eslint-disable-next-line max-len
-  router.get('/pages', accessTokenParser, loginRequiredStrictly, ...validator.getPagesAroundTarget, async(req: AuthorizedRequest, res: ApiV3Response): Promise<any> => {
+  router.get('/siblings', accessTokenParser, loginRequiredStrictly, ...validator.pageIdAndPathRequired, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response): Promise<any> => {
     const { id, path } = req.query;
 
     const Page: PageModel = crowi.model('Page');
 
     let siblings: PageDocument[];
-    let ancestors: PageDocument[];
     let target: PageDocument;
     try {
       siblings = await Page.findSiblingsByPathAndViewer(path as string, req.user);
-      ancestors = await Page.findAncestorsByPath(path as string);
 
       target = siblings.filter(page => page._id.toString() === id)?.[0];
       if (target == null) {
@@ -69,7 +70,29 @@ export default (crowi: Crowi): Router => {
       siblings = siblings.filter(page => !isTopPage(page.path));
     }
 
-    return res.apiv3({ target, ancestors, pages: siblings });
+    return res.apiv3({ target, siblings });
+  });
+
+  // eslint-disable-next-line max-len
+  router.get('/ancestors', accessTokenParser, loginRequiredStrictly, validator.pageIdOrPathRequired, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response): Promise<any> => {
+    const { id, path } = req.query;
+
+    const Page: PageModel = crowi.model('Page');
+
+    let ancestors: PageDocument[];
+    try {
+      ancestors = await Page.findAncestorsByPathOrId((path || id) as string);
+
+      if (ancestors.length === 0 && !isTopPage(path as string)) {
+        throw Error('Ancestors must have at least one page.');
+      }
+    }
+    catch (err) {
+      logger.error('Error occurred while finding pages.', err);
+      return res.apiv3Err(new ErrorV3('Error occurred while finding pages.'));
+    }
+
+    return res.apiv3({ ancestors });
   });
 
   return router;