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

Merge pull request #8763 from weseek/support/improve-creator-type-in-ipage-interface

support: Improve types in IPage and IRevision
Yuki Takei 1 год назад
Родитель
Сommit
cd42e0683e

+ 2 - 2
apps/app/src/client/services/layout.ts

@@ -1,4 +1,4 @@
-import type { IPage } from '@growi/core';
+import type { IPage, IPagePopulatedToShowRevision } from '@growi/core';
 
 import { useIsContainerFluid } from '~/stores/context';
 import { useEditorMode } from '~/stores/ui';
@@ -16,7 +16,7 @@ const useDetermineExpandContent = (expandContentWidth?: boolean | null): boolean
   return expandContentWidth ?? isContainerFluidDefault ?? false;
 };
 
-export const useShouldExpandContent = (data?: IPage | boolean | null): boolean => {
+export const useShouldExpandContent = (data?: IPage | IPagePopulatedToShowRevision | boolean | null): boolean => {
   const expandContentWidth = (() => {
     // when data is null
     if (data == null) {

+ 17 - 6
apps/app/src/components/AuthorInfo/AuthorInfo.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
 
-import type { IUser } from '@growi/core';
+import type { IUserHasId } from '@growi/core';
+import { isPopulated, type IUser, type Ref } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { UserPicture } from '@growi/ui/dist/components';
 import { format } from 'date-fns/format';
@@ -10,10 +11,22 @@ import Link from 'next/link';
 
 import styles from './AuthorInfo.module.scss';
 
+const UserLabel = ({ user }: { user: IUserHasId | Ref<IUser> }): JSX.Element => {
+  if (isPopulated(user)) {
+    return (
+      <Link href={pagePathUtils.userHomepagePath(user)} prefetch={false}>
+        {user.name}
+      </Link>
+    );
+  }
+
+  return <i>(anyone)</i>;
+};
+
 
-export type AuthorInfoProps = {
+type AuthorInfoProps = {
   date: Date,
-  user: IUser,
+  user?: IUserHasId | Ref<IUser>,
   mode: 'create' | 'update',
   locate: 'subnav' | 'footer',
 }
@@ -37,9 +50,7 @@ export const AuthorInfo = (props: AuthorInfoProps): JSX.Element => {
     : t('author_info.last_revision_posted_at');
   const userLabel = user != null
     ? (
-      <Link href={pagePathUtils.userHomepagePath(user)} prefetch={false}>
-        {user.name}
-      </Link>
+      <UserLabel user={user} />
     )
     : <i>Unknown</i>;
 

+ 1 - 1
apps/app/src/components/ContentLinkButtons.tsx

@@ -40,7 +40,7 @@ RecentlyCreatedLinkButton.displayName = 'RecentlyCreatedLinkButton';
 
 
 export type ContentLinkButtonsProps = {
-  author: IUserHasId | null,
+  author?: IUserHasId,
 }
 
 export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => {

+ 1 - 1
apps/app/src/components/PageAttachment/DeleteAttachmentModal.tsx

@@ -76,7 +76,7 @@ export const DeleteAttachmentModal: React.FC = () => {
           <span className="material-symbols-outlined">{iconByFormat(attachment.fileFormat)}</span> {attachment.originalName}
         </p>
         <p>
-          uploaded by <UserPicture user={attachment.creator} size="sm"></UserPicture> <Username user={attachment.creator as IUser}></Username>
+          uploaded by <UserPicture user={attachment.creator} size="sm"></UserPicture> <Username user={attachment.creator}></Username>
         </p>
         {content}
       </div>

+ 2 - 2
apps/app/src/components/PageAuthorInfo/PageAuthorInfo.tsx

@@ -33,12 +33,12 @@ export const PageAuthorInfo = memo((): JSX.Element => {
     <ul className={`grw-page-author-info ${styles['grw-page-author-info']} text-nowrap border-start d-none d-lg-block d-edit-none py-2 ps-4 mb-0 ms-3`}>
       <li className="pb-1">
         {currentPage != null && (
-          <AuthorInfo user={currentPage.creator as IUser} date={currentPage.createdAt} mode="create" locate="subnav" />
+          <AuthorInfo user={currentPage.creator} date={currentPage.createdAt} mode="create" locate="subnav" />
         )}
       </li>
       <li className="mt-1 pt-1 border-top">
         {currentPage != null && (
-          <AuthorInfo user={currentPage.lastUpdateUser as IUser} date={currentPage.updatedAt} mode="update" locate="subnav" />
+          <AuthorInfo user={currentPage.lastUpdateUser} date={currentPage.updatedAt} mode="update" locate="subnav" />
         )}
       </li>
     </ul>

+ 5 - 7
apps/app/src/components/PageContentFooter.tsx

@@ -1,16 +1,14 @@
 import React from 'react';
 
-import type { IPage, IUser } from '@growi/core';
+import type { IPage, IPagePopulatedToShowRevision } from '@growi/core';
 import dynamic from 'next/dynamic';
 
-import type { AuthorInfoProps } from './AuthorInfo';
-
 import styles from './PageContentFooter.module.scss';
 
-const AuthorInfo = dynamic<AuthorInfoProps>(() => import('./AuthorInfo').then(mod => mod.AuthorInfo), { ssr: false });
+const AuthorInfo = dynamic(() => import('./AuthorInfo').then(mod => mod.AuthorInfo), { ssr: false });
 
 export type PageContentFooterProps = {
-  page: IPage,
+  page: IPage | IPagePopulatedToShowRevision,
 }
 
 export const PageContentFooter = (props: PageContentFooterProps): JSX.Element => {
@@ -29,8 +27,8 @@ export const PageContentFooter = (props: PageContentFooterProps): JSX.Element =>
     <div className={`${styles['page-content-footer']} page-content-footer py-4 d-edit-none d-print-none}`}>
       <div className="container-lg grw-container-convertible">
         <div className="page-meta">
-          <AuthorInfo user={creator as IUser} date={createdAt} mode="create" locate="footer" />
-          <AuthorInfo user={lastUpdateUser as IUser} date={updatedAt} mode="update" locate="footer" />
+          <AuthorInfo user={creator} date={createdAt} mode="create" locate="footer" />
+          <AuthorInfo user={lastUpdateUser} date={updatedAt} mode="update" locate="footer" />
         </div>
       </div>
     </div>

+ 5 - 4
apps/app/src/components/User/Username.tsx

@@ -1,13 +1,14 @@
 import React from 'react';
 
-import type { IUser } from '@growi/core';
+import type { IUserHasId } from '@growi/core';
+import { isPopulated, type IUser, type Ref } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import Link from 'next/link';
 
-export const Username: React.FC<{ user?: IUser }> = ({ user }): JSX.Element => {
+export const Username: React.FC<{ user?: IUserHasId | Ref<IUser> }> = ({ user }): JSX.Element => {
 
-  if (user == null) {
-    return <span>anyone</span>;
+  if (user == null || !isPopulated(user)) {
+    return <i>(anyone)</i>;
   }
 
   const name = user.name || '(no name)';

+ 9 - 3
apps/app/src/server/routes/apiv3/page-listing.ts

@@ -1,11 +1,11 @@
 import type {
   IPageInfoForListing, IPageInfo,
 } from '@growi/core';
-import { isIPageInfoForEntity } from '@growi/core';
+import { getIdForRef, isIPageInfoForEntity } from '@growi/core';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, Router } from 'express';
 import express from 'express';
-import { query, oneOf, validationResult } from 'express-validator';
+import { query, oneOf } from 'express-validator';
 import mongoose from 'mongoose';
 
 
@@ -157,7 +157,13 @@ const routerFactory = (crowi: Crowi): Router => {
         const basicPageInfo = pageService.constructBasicPageInfo(page, isGuestUser);
 
         // TODO: use pageService.getCreatorIdForCanDelete to get creatorId (https://redmine.weseek.co.jp/issues/140574)
-        const canDeleteCompletely = pageService.canDeleteCompletely(page, page.creator, req.user, false, userRelatedGroups); // use normal delete config
+        const canDeleteCompletely = pageService.canDeleteCompletely(
+          page,
+          page.creator == null ? null : getIdForRef(page.creator),
+          req.user,
+          false,
+          userRelatedGroups,
+        ); // use normal delete config
 
         const pageInfo = (!isIPageInfoForEntity(basicPageInfo))
           ? basicPageInfo

+ 4 - 3
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -1,4 +1,4 @@
-import { Origin, allOrigin } from '@growi/core';
+import { Origin, allOrigin, getIdForRef } from '@growi/core';
 import type {
   IPage, IRevisionHasId, IUserHasId,
 } from '@growi/core';
@@ -89,6 +89,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
     }
 
     // persist activity
+    const creator = updatedPage.creator != null ? getIdForRef(updatedPage.creator) : undefined;
     const parameters = {
       targetModel: SupportedTargetModel.MODEL_PAGE,
       target: updatedPage,
@@ -97,7 +98,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
     const activityEvent = crowi.event('activity');
     activityEvent.emit(
       'update', res.locals.activity._id, parameters,
-      { path: updatedPage.path, creator: updatedPage.creator._id.toString() },
+      { path: updatedPage.path, creator },
       preNotifyService.generatePreNotify,
     );
 
@@ -157,7 +158,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
         return res.apiv3Err(new ErrorV3('Posted param "revisionId" is outdated.', PageUpdateErrorCode.CONFLICT, undefined, { returnLatestRevision }), 409);
       }
 
-      let updatedPage;
+      let updatedPage: PageDocument;
       try {
         const {
           grant, userRelatedGrantUserGroupIds, overwriteScopesOfDescendants, wip,

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

@@ -263,10 +263,14 @@ class PageService implements IPageService {
     if (page.isEmpty) {
       const Page = mongoose.model<IPage, PageModel>('Page');
       const notEmptyClosestAncestor = await Page.findNonEmptyClosestAncestor(page.path);
-      return notEmptyClosestAncestor?.creator ?? null;
+      return notEmptyClosestAncestor?.creator == null
+        ? null
+        : getIdForRef(notEmptyClosestAncestor.creator);
     }
 
-    return page.creator ?? null;
+    return page.creator == null
+      ? null
+      : getIdForRef(page.creator);
   }
 
   // Use getCreatorIdForCanDelete before execution of canDelete to get creatorId.
@@ -351,13 +355,19 @@ class PageService implements IPageService {
       user: IUserHasId,
       isRecursively: boolean,
       canDeleteFunction: (
-        page: PageDocument, creatorId: ObjectIdLike, operator: any, isRecursively: boolean, userRelatedGroups: PopulatedGrantedGroup[]
+        page: PageDocument, creatorId: ObjectIdLike | null, operator: any, isRecursively: boolean, userRelatedGroups: PopulatedGrantedGroup[]
       ) => boolean,
   ): Promise<PageDocument[]> {
     const userRelatedGroups = await this.pageGrantService.getUserRelatedGroups(user);
     const filteredPages = pages.filter(async(p) => {
       if (p.isEmpty) return true;
-      const canDelete = canDeleteFunction(p, p.creator, user, isRecursively, userRelatedGroups);
+      const canDelete = canDeleteFunction(
+        p,
+        p.creator == null ? null : getIdForRef(p.creator),
+        user,
+        isRecursively,
+        userRelatedGroups,
+      );
       return canDelete;
     });
 

+ 4 - 4
apps/app/src/stores/remote-latest-page.ts

@@ -1,6 +1,6 @@
 import { useMemo, useCallback } from 'react';
 
-import type { IUser } from '@growi/core';
+import type { IUserHasId } from '@growi/core';
 import { useSWRStatic } from '@growi/core/dist/swr';
 import type { SWRResponse } from 'swr';
 
@@ -13,8 +13,8 @@ export const useRemoteRevisionBody = (initialData?: string): SWRResponse<string,
   return useSWRStatic<string, Error>('remoteRevisionBody', initialData);
 };
 
-export const useRemoteRevisionLastUpdateUser = (initialData?: IUser): SWRResponse<IUser, Error> => {
-  return useSWRStatic<IUser, Error>('remoteRevisionLastUpdateUser', initialData);
+export const useRemoteRevisionLastUpdateUser = (initialData?: IUserHasId): SWRResponse<IUserHasId, Error> => {
+  return useSWRStatic<IUserHasId, Error>('remoteRevisionLastUpdateUser', initialData);
 };
 
 export const useRemoteRevisionLastUpdatedAt = (initialData?: Date): SWRResponse<Date, Error> => {
@@ -24,7 +24,7 @@ export const useRemoteRevisionLastUpdatedAt = (initialData?: Date): SWRResponse<
 export type RemoteRevisionData = {
   remoteRevisionId: string,
   remoteRevisionBody: string,
-  remoteRevisionLastUpdateUser: IUser,
+  remoteRevisionLastUpdateUser?: IUserHasId,
   remoteRevisionLastUpdatedAt: Date,
 }
 

+ 2 - 2
packages/core/src/interfaces/common.ts

@@ -10,11 +10,11 @@ export type Ref<T> = string | T & HasObjectId;
 
 export type Nullable<T> = T | null | undefined;
 
-export const isPopulated = <T>(ref: Ref<T>): ref is T & HasObjectId => {
+export const isPopulated = <T>(ref: T & HasObjectId | Ref<T>): ref is T & HasObjectId => {
   return !(typeof ref === 'string');
 };
 
-export const getIdForRef = <T>(ref: Ref<T>): string => {
+export const getIdForRef = <T>(ref: T & HasObjectId | Ref<T>): string => {
   return isPopulated(ref)
     ? ref._id
     : ref;

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

@@ -20,7 +20,7 @@ export type IPage = {
   status: string,
   revision?: Ref<IRevision>,
   tags: Ref<ITag>[],
-  creator: any,
+  creator?: Ref<IUser>,
   createdAt: Date,
   updatedAt: Date,
   seenUsers: Ref<IUser>[],
@@ -30,7 +30,7 @@ export type IPage = {
   grant: PageGrant,
   grantedUsers: Ref<IUser>[],
   grantedGroups: IGrantedGroup[],
-  lastUpdateUser: Ref<IUser>,
+  lastUpdateUser?: Ref<IUser>,
   liker: Ref<IUser>[],
   commentCount: number
   slackChannels: string,
@@ -43,13 +43,9 @@ export type IPage = {
   ttlTimestamp?: Date
 }
 
-export type IPagePopulatedToList = Omit<IPageHasId, 'lastUpdateUser'> & {
-  lastUpdateUser: IUserHasId,
-}
-
 export type IPagePopulatedToShowRevision = Omit<IPageHasId, 'lastUpdateUser'|'creator'|'deleteUser'|'grantedGroups'|'revision'|'author'> & {
-  lastUpdateUser: IUserHasId,
-  creator: IUserHasId | null,
+  lastUpdateUser?: IUserHasId,
+  creator?: IUserHasId,
   deleteUser: IUserHasId,
   grantedGroups: { type: GroupType, item: IUserGroupHasId }[],
   revision?: IRevisionHasId,

+ 2 - 1
packages/core/src/interfaces/revision.ts

@@ -1,3 +1,4 @@
+import type { Ref } from './common';
 import type { HasObjectId } from './has-object-id';
 import type { IUser } from './user';
 
@@ -12,7 +13,7 @@ export const allOrigin = Object.values(Origin);
 
 export type IRevision = {
   body: string,
-  author: IUser,
+  author: Ref<IUser>,
   hasDiffToPrev: boolean;
   createdAt: Date,
   updatedAt: Date,