Ver Fonte

refactor findPageAndMetaDataByViewer

Yuki Takei há 6 meses atrás
pai
commit
4fda942501

+ 4 - 4
apps/app/src/pages/[[...path]]/page-data-props.ts

@@ -1,7 +1,7 @@
 import assert from 'assert';
 
 import type {
-  IDataWithMeta, IPage, IPageNotFoundInfo, IUser,
+  IDataWithRequiredMeta, IPage, IPageNotFoundInfo, IUser,
 } from '@growi/core/dist/interfaces';
 import {
   isIPageInfo,
@@ -133,7 +133,7 @@ export async function getPageDataForInitial(
   }
 
   // Get full page data
-  const pageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, resolvedPagePath, user, true);
+  const pageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, resolvedPagePath, user);
 
   // Handle URL conversion
   const currentPathname = resolveFinalizedPathname(resolvedPagePath, pageWithMeta.data, isPermalink);
@@ -157,7 +157,7 @@ export async function getPageDataForInitial(
               isNotFound: true,
               isForbidden: false,
             },
-          } satisfies IDataWithMeta<null, IPageNotFoundInfo>,
+          } satisfies IDataWithRequiredMeta<null, IPageNotFoundInfo>,
           skipSSR: false,
           redirectFrom,
         },
@@ -201,7 +201,7 @@ export async function getPageDataForInitial(
     props: {
       currentPathname: resolvedPagePath,
       isIdenticalPathPage: false,
-      pageWithMeta: pageWithMeta satisfies IDataWithMeta<null, IPageNotFoundInfo>,
+      pageWithMeta: pageWithMeta satisfies IDataWithRequiredMeta<null, IPageNotFoundInfo>,
       skipSSR: false,
       redirectFrom,
     },

+ 4 - 6
apps/app/src/pages/general-page/superjson/page-to-show-revision-with-meta.ts

@@ -1,11 +1,9 @@
-import type {
-  IDataWithMeta,
-} from '@growi/core';
+import type { IDataWithRequiredMeta } from '@growi/core';
 import superjson from 'superjson';
 
 import type { IPageToShowRevisionWithMeta } from '../types';
 
-type IPageToShowRevisionWithMetaSerialized = IDataWithMeta<string, string | undefined>;
+type IPageToShowRevisionWithMetaSerialized = IDataWithRequiredMeta<string, string>;
 
 let isRegistered = false;
 
@@ -23,13 +21,13 @@ export const registerPageToShowRevisionWithMeta = (): void => {
       serialize: (v) => {
         return {
           data: superjson.stringify(v.data.toObject()),
-          meta: v.meta != null ? superjson.stringify(v.meta) : undefined,
+          meta: superjson.stringify(v.meta),
         };
       },
       deserialize: (v) => {
         return {
           data: superjson.parse(v.data),
-          meta: v.meta != null ? superjson.parse(v.meta) : undefined,
+          meta: superjson.parse(v.meta),
         };
       },
     },

+ 3 - 3
apps/app/src/pages/general-page/types.ts

@@ -1,11 +1,11 @@
 import type {
-  IDataWithMeta, IPageInfoExt, IPageNotFoundInfo, IPagePopulatedToShowRevision,
+  IDataWithRequiredMeta, IPageInfoExt, IPageNotFoundInfo, IPagePopulatedToShowRevision,
 } from '@growi/core';
 
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { PageDocument } from '~/server/models/page';
 
-export type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfoExt>;
+export type IPageToShowRevisionWithMeta = IDataWithRequiredMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfoExt>;
 
 export type RendererConfigProps = {
   rendererConfig: RendererConfig,
@@ -43,6 +43,6 @@ export type GeneralPageEachProps = {};
 
 // Do not include CommonEachProps for multi stage
 export type GeneralPageInitialProps = RendererConfigProps & ServerConfigurationProps & {
-  pageWithMeta: IPageToShowRevisionWithMeta | IDataWithMeta<null, IPageNotFoundInfo> | null,
+  pageWithMeta: IPageToShowRevisionWithMeta | IDataWithRequiredMeta<null, IPageNotFoundInfo> | null,
   skipSSR?: boolean,
 }

+ 2 - 3
apps/app/src/pages/share/[[...path]]/index.page.tsx

@@ -43,12 +43,11 @@ const isInitialProps = (props: Props): props is InitialProps => {
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
   // Initialize Jotai atoms with initial data - must be called unconditionally
-  const pageData = isInitialProps(props) ? props.pageWithMeta?.data : undefined;
-  const pageMeta = isInitialProps(props) ? props.pageWithMeta?.meta : undefined;
+  const pageData = isInitialProps(props) ? props.page : undefined;
   const shareLink = isInitialProps(props) ? props.shareLink : undefined;
   const isExpired = isInitialProps(props) ? props.isExpired : undefined;
 
-  useHydratePageAtoms(pageData, pageMeta, {
+  useHydratePageAtoms(pageData, undefined, {
     shareLinkId: shareLink?._id,
   });
 

+ 4 - 4
apps/app/src/pages/share/[[...path]]/page-data-props.ts

@@ -38,7 +38,7 @@ export const getPageDataForInitial = async(context: GetServerSidePropsContext):
     return {
       props: {
         isNotFound: true,
-        pageWithMeta: null,
+        page: null,
         isExpired: undefined,
         shareLink: undefined,
       },
@@ -50,7 +50,7 @@ export const getPageDataForInitial = async(context: GetServerSidePropsContext):
     return {
       props: {
         isNotFound: false,
-        pageWithMeta: null,
+        page: null,
         isExpired: true,
         shareLink,
       },
@@ -65,7 +65,7 @@ export const getPageDataForInitial = async(context: GetServerSidePropsContext):
     return {
       props: {
         isNotFound: true,
-        pageWithMeta: null,
+        page: null,
         isExpired: undefined,
         shareLink: undefined,
       },
@@ -84,7 +84,7 @@ export const getPageDataForInitial = async(context: GetServerSidePropsContext):
   return {
     props: {
       isNotFound: false,
-      pageWithMeta: { data: populatedPage },
+      page: populatedPage,
       skipSSR,
       isExpired: false,
       shareLink: shareLink.toObject(),

+ 7 - 2
apps/app/src/pages/share/[[...path]]/types.ts

@@ -1,17 +1,22 @@
+import type { IPagePopulatedToShowRevision } from '@growi/core/dist/interfaces';
+
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import type { CommonEachProps, CommonInitialProps } from '~/pages/common-props';
 import type { GeneralPageInitialProps } from '~/pages/general-page';
 
-export type ShareLinkPageStatesProps = Pick<GeneralPageInitialProps, 'pageWithMeta' | 'skipSSR'> & (
+export type ShareLinkPageStatesProps = Pick<GeneralPageInitialProps, 'skipSSR'> & (
   {
+    page: null,
     isNotFound: true,
     isExpired: undefined,
     shareLink: undefined,
   } | {
+    page: null,
     isNotFound: false,
     isExpired: true,
     shareLink: IShareLinkHasId,
   } | {
+    page: IPagePopulatedToShowRevision,
     isNotFound: false,
     isExpired: false,
     shareLink: IShareLinkHasId,
@@ -19,7 +24,7 @@ export type ShareLinkPageStatesProps = Pick<GeneralPageInitialProps, 'pageWithMe
 );
 
 export type Stage2EachProps = ShareLinkPageStatesProps;
-export type Stage2InitialProps = Stage2EachProps & GeneralPageInitialProps;
+export type Stage2InitialProps = Stage2EachProps & Omit<GeneralPageInitialProps, 'pageWithMeta'>;
 
 export type EachProps = CommonEachProps & Stage2EachProps;
 export type InitialProps = CommonEachProps & CommonInitialProps & Stage2InitialProps;

+ 11 - 12
apps/app/src/server/routes/apiv3/page/index.ts

@@ -6,6 +6,7 @@ import type {
   IDataWithMeta, IPage, IPageInfoExt, IPageNotFoundInfo, IRevision,
 } from '@growi/core';
 import {
+  getIdStringForRef,
   isIPageNotFoundInfo,
   AllSubscriptionStatusType, PageGrant, SCOPE, SubscriptionStatusType,
   getIdForRef,
@@ -19,6 +20,7 @@ import sanitize from 'sanitize-filename';
 
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import type { IPageGrantData } from '~/interfaces/page';
+import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
@@ -73,8 +75,7 @@ const router = express.Router();
  *            description: boolean for like status
  *
  */
-/** @param {import('~/server/crowi').default} crowi Crowi instance */
-module.exports = (crowi) => {
+module.exports = (crowi: Crowi) => {
   const loginRequired = require('../../../middlewares/login-required')(crowi, true);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
   const certifySharedPage = require('../../../middlewares/certify-shared-page')(crowi);
@@ -196,11 +197,10 @@ module.exports = (crowi) => {
           if (shareLink == null) {
             return res.apiv3Err('ShareLink is not found', 404);
           }
-          // page = await Page.findOne({ _id: getIdForRef(shareLink.relatedPage) });
-          pageWithMeta = await pageService.findPageAndMetaDataByShareLink(getIdForRef(shareLink.relatedPage), path, user, false, true);
+          pageWithMeta = await pageService.findPageAndMetaDataByViewer(getIdStringForRef(shareLink.relatedPage), path, user, true);
         }
         else if (!findAll) {
-          pageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, path, user, false);
+          pageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, path, user);
         }
         else {
           pages = await Page.findByPathAndViewer(path, user, null, false, includeEmpty);
@@ -497,13 +497,13 @@ module.exports = (crowi) => {
     const { pageId } = req.query;
 
     try {
-      const pageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, null, user, true, isSharedPage);
+      const { meta } = await pageService.findPageAndMetaDataByViewer(pageId, null, user, isSharedPage);
 
-      if (pageWithMeta == null) {
+      if (isIPageNotFoundInfo(meta)) {
         return res.apiv3Err(`Page '${pageId}' is not found or forbidden`);
       }
 
-      return res.apiv3(pageWithMeta.meta);
+      return res.apiv3(meta);
     }
     catch (err) {
       logger.error('get-page-info', err);
@@ -719,7 +719,7 @@ module.exports = (crowi) => {
     async(req, res) => {
       const { pageId } = req.query;
 
-      const Page = crowi.model('Page');
+      const Page = mongoose.model<IPage, PageModel>('Page');
       const page = await Page.findByIdAndViewer(pageId, req.user, null);
 
       if (page == null) {
@@ -782,7 +782,7 @@ module.exports = (crowi) => {
       const { pageId } = req.params;
       const { grant, userRelatedGrantedGroups } = req.body;
 
-      const Page = crowi.model('Page');
+      const Page = mongoose.model<IPage, PageModel>('Page');
 
       const page = await Page.findByIdAndViewer(pageId, req.user, null, false);
 
@@ -793,9 +793,8 @@ module.exports = (crowi) => {
 
       let data;
       try {
-        const shouldUseV4Process = false;
         const grantData = { grant, userRelatedGrantedGroups };
-        data = await crowi.pageService.updateGrant(page, req.user, grantData, shouldUseV4Process);
+        data = await crowi.pageService.updateGrant(page, req.user, grantData);
       }
       catch (err) {
         logger.error('Error occurred while processing calcApplicableGrantData.', err);

+ 18 - 27
apps/app/src/server/service/page/index.ts

@@ -1,3 +1,4 @@
+import assert from 'assert';
 import type EventEmitter from 'events';
 import pathlib from 'path';
 import { Readable, Writable } from 'stream';
@@ -11,7 +12,8 @@ import { PageGrant, isIPageInfoForEntity } from '@growi/core/dist/interfaces';
 import type {
   Ref, HasObjectId, IUserHasId, IUser,
   IPage, IGrantedGroup, IRevisionHasId,
-  IDataWithMeta, IPageNotFoundInfo, IPageInfoExt, IPageInfo, IPageInfoForEntity, IPageInfoForOperation,
+  IPageNotFoundInfo, IPageInfoExt, IPageInfo, IPageInfoForEntity, IPageInfoForOperation,
+  IDataWithRequiredMeta,
 } from '@growi/core/dist/interfaces';
 import {
   pagePathUtils, pathUtils,
@@ -402,15 +404,15 @@ class PageService implements IPageService {
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   async findPageAndMetaDataByViewer(
-      pageId: string | null,
-      path: string,
+      pageId: string | null, // either pageId or path must be specified
+      path: string | null, // either pageId or path must be specified
       user?: HydratedDocument<IUser>,
-      includeEmpty = false,
       isSharedPage = false,
   ): Promise<
-    IDataWithMeta<HydratedDocument<PageDocument>, IPageInfoExt> |
-    IDataWithMeta<null, IPageNotFoundInfo>
+    IDataWithRequiredMeta<HydratedDocument<PageDocument>, IPageInfoExt> |
+    IDataWithRequiredMeta<null, IPageNotFoundInfo>
   > {
+    assert(pageId != null || path != null);
 
     const Page = mongoose.model<HydratedDocument<PageDocument>, PageModel>('Page');
 
@@ -422,6 +424,7 @@ class PageService implements IPageService {
       page = await Page.findByPathAndViewer(path, user, null, true, true);
     }
 
+    // not found or forbidden
     if (page == null) {
       const count = pageId != null ? await Page.count({ _id: pageId }) : await Page.count({ path });
       const isForbidden = count > 0;
@@ -434,40 +437,28 @@ class PageService implements IPageService {
       };
     }
 
-    if (page.isEmpty && !includeEmpty) {
-      const isVisible = await Page.countByIdAndViewer(page._id, user, null, true);
-      return {
-        data: null,
-        meta: {
-          isNotFound: true,
-          isForbidden: !isVisible,
-        } satisfies IPageNotFoundInfo,
-      };
-    }
+    const isGuestUser = user == null;
+    const basicPageInfo = this.constructBasicPageInfo(page, isGuestUser);
 
     if (isSharedPage) {
       return {
         data: page,
         meta: {
-          isNotFound: page.isEmpty,
-          isV5Compatible: isTopPage(page.path) || page.parent != null,
-          isEmpty: page.isEmpty,
+          ...basicPageInfo,
           isMovable: false,
           isDeletable: false,
           isAbleToDeleteCompletely: false,
           isRevertible: false,
           bookmarkCount: 0,
-        },
+        } satisfies IPageInfo | IPageInfoForEntity,
       };
     }
 
-    const isGuestUser = user == null;
-
     const Bookmark = mongoose.model<BookmarkedPage, { countDocuments, findByPageIdAndUserId }>('Bookmark');
     const bookmarkCount: number = await Bookmark.countDocuments({ page: pageId });
 
     const pageInfo = {
-      ...this.constructBasicPageInfo(page, isGuestUser),
+      ...basicPageInfo,
       bookmarkCount,
     } satisfies IPageInfo | IPageInfoForEntity;
 
@@ -4085,11 +4076,11 @@ class PageService implements IPageService {
 
   /**
    * A wrapper method of updatePage for updating grant only.
-   * @param {PageDocument} page
-   * @param {UserDocument} user
-   * @param options
    */
-  async updateGrant(page, user, grantData: {grant: PageGrant, userRelatedGrantedGroups: IGrantedGroup[]}): Promise<PageDocument> {
+  async updateGrant(
+      page: HydratedDocument<PageDocument>, user: IUserHasId, grantData: {grant: PageGrant, userRelatedGrantedGroups: IGrantedGroup[]},
+  ): Promise<PageDocument> {
+
     const { grant, userRelatedGrantedGroups } = grantData;
 
     const options: IOptionsForUpdate = {

+ 10 - 4
apps/app/src/server/service/page/page-service.ts

@@ -2,9 +2,9 @@ import type EventEmitter from 'events';
 
 import type {
   HasObjectId,
-  IDataWithMeta,
+  IDataWithRequiredMeta,
   IGrantedGroup,
-  IPageInfo, IPageInfoForEntity, IPageNotFoundInfo, IUser, IPageInfoExt, IPage,
+  IPageInfo, IPageInfoForEntity, IPageNotFoundInfo, IUser, IPageInfoExt, IPage, PageGrant, IUserHasId,
 } from '@growi/core';
 import type { HydratedDocument, Types } from 'mongoose';
 
@@ -25,12 +25,18 @@ export interface IPageService {
     pageData: HydratedDocument<PageDocument>, body: string | null, previousBody: string | null, user: IUser, options: IOptionsForUpdate
   ): Promise<HydratedDocument<PageDocument>>,
   updateDescendantCountOfAncestors: (pageId: ObjectIdLike, inc: number, shouldIncludeTarget: boolean) => Promise<void>,
+  updateGrant(
+    page: HydratedDocument<PageDocument>, user: IUserHasId, grantData: {grant: PageGrant, userRelatedGrantedGroups: IGrantedGroup[]},
+  ): Promise<PageDocument>,
   deleteCompletelyOperation: (pageIds: ObjectIdLike[], pagePaths: string[]) => Promise<void>,
   getEventEmitter: () => EventEmitter,
   deleteMultipleCompletely: (pages: ObjectIdLike[], user: IUser | undefined) => Promise<void>,
   findPageAndMetaDataByViewer(
-      pageId: string | null, path: string, user?: HydratedDocument<IUser>, includeEmpty?: boolean, isSharedPage?: boolean,
-  ): Promise<IDataWithMeta<HydratedDocument<PageDocument>, IPageInfoExt> | IDataWithMeta<null, IPageNotFoundInfo>>
+      pageId: string, path: string | null, user?: HydratedDocument<IUser>, isSharedPage?: boolean,
+  ): Promise<IDataWithRequiredMeta<HydratedDocument<PageDocument>, IPageInfoExt> | IDataWithRequiredMeta<null, IPageNotFoundInfo>>
+  findPageAndMetaDataByViewer(
+      pageId: string | null, path: string, user?: HydratedDocument<IUser>, isSharedPage?: boolean,
+  ): Promise<IDataWithRequiredMeta<HydratedDocument<PageDocument>, IPageInfoExt> | IDataWithRequiredMeta<null, IPageNotFoundInfo>>
   resumeRenameSubOperation(renamedPage: PageDocument, pageOp: PageOperationDocument, activity?): Promise<void>
   handlePrivatePagesForGroupsToDelete(
     groupsToDelete: UserGroupDocument[] | ExternalUserGroupDocument[],

+ 4 - 0
packages/core/src/interfaces/page.ts

@@ -176,6 +176,10 @@ export type IDataWithMeta<D = unknown, M = unknown> = {
   data: D;
   meta?: M;
 };
+export type IDataWithRequiredMeta<D = unknown, M = unknown> = IDataWithMeta<
+  D,
+  M
+> & { meta: M };
 
 export type IPageWithMeta<M = IPageInfoExt> = IDataWithMeta<IPageHasId, M>;