Просмотр исходного кода

fix: enhance page info types to handle empty pages and improve type safety

Yuki Takei 3 месяцев назад
Родитель
Сommit
18e0921fcc
2 измененных файлов с 36 добавлено и 13 удалено
  1. 13 9
      apps/app/src/server/service/page/index.ts
  2. 23 4
      packages/core/src/interfaces/page.ts

+ 13 - 9
apps/app/src/server/service/page/index.ts

@@ -8,11 +8,11 @@ import {
   PageStatus, YDocStatus, getIdForRef,
   getIdStringForRef,
 } from '@growi/core';
-import { PageGrant, isIPageInfoForEntity } from '@growi/core/dist/interfaces';
+import { PageGrant, isIPageInfoForEmpty, isIPageInfoForEntity } from '@growi/core/dist/interfaces';
 import type {
   Ref, HasObjectId, IUserHasId, IUser,
   IPage, IGrantedGroup, IRevisionHasId,
-  IPageNotFoundInfo, IPageInfoExt, IPageInfo, IPageInfoForEntity, IPageInfoForOperation,
+  IPageNotFoundInfo, IPageInfoExt, IPageInfo, IPageInfoForEmpty, IPageInfoForEntity, IPageInfoForOperation,
   IDataWithRequiredMeta,
 } from '@growi/core/dist/interfaces';
 import {
@@ -460,7 +460,7 @@ class PageService implements IPageService {
     const pageInfo = {
       ...basicPageInfo,
       bookmarkCount,
-    } satisfies IPageInfo | IPageInfoForEntity;
+    } satisfies | IPageInfo | IPageInfoForEntity;
 
     if (isGuestUser) {
       return {
@@ -475,21 +475,25 @@ class PageService implements IPageService {
 
     const isDeletable = this.canDelete(page, creatorId, user, false);
     const isAbleToDeleteCompletely = this.canDeleteCompletely(page, creatorId, user, false, userRelatedGroups); // use normal delete config
+    const isBookmarked: boolean = isGuestUser
+      ? false
+      : (await Bookmark.findByPageIdAndUserId(pageId, user._id)) != null;
 
-    if (!isIPageInfoForEntity(pageInfo)) {
+    if (isIPageInfoForEmpty(pageInfo)) {
       return {
         data: page,
         meta: {
           ...pageInfo,
           isDeletable,
           isAbleToDeleteCompletely,
-        } satisfies IPageInfo,
+          isBookmarked,
+        } satisfies IPageInfoForEmpty,
       };
     }
 
-    const isBookmarked: boolean = isGuestUser
-      ? false
-      : (await Bookmark.findByPageIdAndUserId(pageId, user._id)) != null;
+    // IPageInfoForEmpty and IPageInfoForEntity are mutually exclusive
+    // so hereafter we can safely
+    assert(isIPageInfoForEntity(pageInfo));
 
     const isLiked: boolean = page.isLiked(user);
     const subscription = await Subscription.findByUserIdAndTargetId(user._id, page._id);
@@ -2578,7 +2582,7 @@ class PageService implements IPageService {
         isV5Compatible: true,
         isEmpty: true,
         isMovable,
-        isDeletable: false,
+        isDeletable,
         isAbleToDeleteCompletely: false,
         isRevertible: false,
       };

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

@@ -83,10 +83,14 @@ export type PageStatus = (typeof PageStatus)[keyof typeof PageStatus];
 
 export type IPageHasId = IPage & HasObjectId;
 
-export type IPageNotFoundInfo = {
-  isNotFound: true;
+// Special type to represent page is an empty page or not found or forbidden status
+export type IPageNotFoundInfo = (
+  | IPageInfoForEmpty
+  | {
+      isNotFound: true;
+    }
+) & {
   isForbidden: boolean;
-  isEmpty?: true;
 };
 
 export type IPageInfo = {
@@ -100,6 +104,12 @@ export type IPageInfo = {
   bookmarkCount: number;
 };
 
+export type IPageInfoForEmpty = Omit<IPageInfo, 'isNotFound' | 'isEmpty'> & {
+  isNotFound: true;
+  isEmpty: true;
+  isBookmarked?: boolean;
+};
+
 export type IPageInfoForEntity = Omit<IPageInfo, 'isNotFound' | 'isEmpty'> & {
   isNotFound: false;
   isEmpty: false;
@@ -123,6 +133,7 @@ export type IPageInfoForListing = IPageInfoForEntity & HasRevisionShortbody;
 
 export type IPageInfoExt =
   | IPageInfo
+  | IPageInfoForEmpty
   | IPageInfoForEntity
   | IPageInfoForOperation
   | IPageInfoForListing;
@@ -134,7 +145,8 @@ export const isIPageNotFoundInfo = (
   return (
     pageInfo != null &&
     pageInfo instanceof Object &&
-    pageInfo.isNotFound === true
+    pageInfo.isNotFound === true &&
+    'isForbidden' in pageInfo
   );
 };
 
@@ -147,6 +159,13 @@ export const isIPageInfo = (
   );
 };
 
+export const isIPageInfoForEmpty = (
+  // biome-ignore lint/suspicious/noExplicitAny: ignore
+  pageInfo: any | undefined,
+): pageInfo is IPageInfoForEmpty => {
+  return isIPageInfo(pageInfo) && pageInfo.isEmpty === true;
+};
+
 export const isIPageInfoForEntity = (
   // biome-ignore lint/suspicious/noExplicitAny: ignore
   pageInfo: any | undefined,