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

show unrelated granted groups in grant selector

Futa Arai 2 лет назад
Родитель
Сommit
7192efba07

+ 13 - 4
apps/app/src/components/PageAlert/FixPageGrantAlert.tsx

@@ -8,7 +8,7 @@ import {
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import type { IPageGrantData } from '~/interfaces/page';
+import { UserGroupPageGrantStatus, type IPageGrantData } from '~/interfaces/page';
 import type { PopulatedGrantedGroup, IRecordApplicableGrant, IResIsGrantNormalizedGrantData } from '~/interfaces/page-grant';
 import { useCurrentUser } from '~/stores/context';
 import { useSWRxApplicableGrant, useSWRxIsGrantNormalized, useSWRxCurrentPage } from '~/stores/page';
@@ -99,10 +99,19 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
     }
 
     if (grantData.grant === 5) {
-      if (grantData.userRelatedGrantedGroups == null || grantData.userRelatedGrantedGroups.length === 0) {
-        return t('fix_page_grant.modal.grant_label.isForbidden');
+      const groupGrantData = grantData.groupGrantData;
+      if (groupGrantData != null) {
+        const userRelatedGrantedGroups = groupGrantData.userRelatedGroups.filter(group => group.status === UserGroupPageGrantStatus.isGranted);
+        if (userRelatedGrantedGroups.length > 0) {
+          const grantedGroupNames = [
+            ...userRelatedGrantedGroups.map(group => group.name),
+            ...groupGrantData.nonUserRelatedGrantedGroups.map(group => group.name),
+          ];
+          return `${t('fix_page_grant.modal.radio_btn.grant_group')} (${grantedGroupNames.join(', ')})`;
+        }
       }
-      return `${t('fix_page_grant.modal.radio_btn.grant_group')} (${grantData.userRelatedGrantedGroups.map(g => g.name).join(', ')})`;
+
+      return t('fix_page_grant.modal.grant_label.isForbidden');
     }
 
     throw Error('cannot get grant label'); // this error can't be throwed

+ 9 - 9
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -36,7 +36,7 @@ import {
   useWaitingSaveProcessing,
 } from '~/stores/editor';
 import {
-  useCurrentPagePath, useSWRxCurrentPage, useCurrentPageId, useIsNotFound, useTemplateBodyData,
+  useCurrentPagePath, useSWRxCurrentPage, useCurrentPageId, useIsNotFound, useTemplateBodyData, useSWRxIsGrantNormalized,
 } from '~/stores/page';
 import { mutatePageTree } from '~/stores/page-listing';
 import { usePreviewOptions } from '~/stores/renderer';
@@ -96,7 +96,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPage } = useSWRxCurrentPage();
-  const { data: grantData } = useSelectedGrant();
+  const { data: selectedGrant } = useSelectedGrant();
   const { data: editingMarkdown } = useEditingMarkdown();
   const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
   const { data: templateBodyData } = useTemplateBodyData();
@@ -108,6 +108,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { data: defaultIndentSize } = useDefaultIndentSize();
   const { data: acceptedUploadFileType } = useAcceptedUploadFileType();
   const { data: editorSettings } = useEditorSettings();
+  const { mutate: mutateIsGrantNormalized } = useSWRxIsGrantNormalized(currentPage?._id);
   const { data: user } = useCurrentUser();
   const { onEditorsUpdated } = useEditingUsers();
   const onConflict = useConflictResolver();
@@ -164,9 +165,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
 
   const save: Save = useCallback(async(revisionId, markdown, opts, onConflict) => {
-    if (pageId == null || grantData == null) {
+    if (pageId == null || selectedGrant == null) {
       logger.error('Some materials to save are invalid', {
-        pageId, grantData,
+        pageId, selectedGrant,
       });
       throw new Error('Some materials to save are invalid');
     }
@@ -178,16 +179,15 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
         pageId,
         revisionId,
         body: markdown ?? '',
-        grant: grantData?.grant,
+        grant: selectedGrant?.grant,
         origin: Origin.Editor,
-        userRelatedGrantUserGroupIds: grantData?.userRelatedGrantedGroups?.map((group) => {
-          return { item: group.id, type: group.type };
-        }),
+        userRelatedGrantUserGroupIds: selectedGrant?.userRelatedGrantedGroups,
         ...(opts ?? {}),
       });
 
       // to sync revision id with page tree: https://github.com/weseek/growi/pull/7227
       mutatePageTree();
+      mutateIsGrantNormalized();
 
       return page;
     }
@@ -207,7 +207,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     finally {
       mutateWaitingSaveProcessing(false);
     }
-  }, [pageId, grantData, mutateWaitingSaveProcessing, t]);
+  }, [pageId, selectedGrant, mutateWaitingSaveProcessing, t, mutateIsGrantNormalized]);
 
   const saveAndReturnToViewHandler = useCallback(async(opts: SaveOptions) => {
     const markdown = codeMirrorEditor?.getDoc();

+ 2 - 19
apps/app/src/components/SavePageControls.tsx

@@ -10,7 +10,6 @@ import {
 } from 'reactstrap';
 
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import type { IPageGrantData } from '~/interfaces/page';
 import {
   useIsEditable, useIsAclEnabled,
   useIsSlackConfigured,
@@ -19,7 +18,6 @@ import { useWaitingSaveProcessing, useSWRxSlackChannels, useIsSlackEnabled } fro
 import { useSWRMUTxCurrentPage, useSWRxCurrentPage, useCurrentPagePath } from '~/stores/page';
 import { mutatePageTree } from '~/stores/page-listing';
 import {
-  useSelectedGrant,
   useEditorMode, useIsDeviceLargerThanMd,
   EditorMode,
 } from '~/stores/ui';
@@ -154,7 +152,6 @@ export const SavePageControls = (): JSX.Element | null => {
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: isEditable } = useIsEditable();
   const { data: isAclEnabled } = useIsAclEnabled();
-  const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
 
   const { data: editorMode } = useEditorMode();
   const { data: currentPagePath } = useCurrentPagePath();
@@ -184,11 +181,7 @@ export const SavePageControls = (): JSX.Element | null => {
     setSlackChannels(slackChannels);
   }, []);
 
-  const updateGrantHandler = useCallback((grantData: IPageGrantData): void => {
-    mutateGrant(grantData);
-  }, [mutateGrant]);
-
-  if (isEditable == null || isAclEnabled == null || grantData == null) {
+  if (isEditable == null || isAclEnabled == null) {
     return null;
   }
 
@@ -196,8 +189,6 @@ export const SavePageControls = (): JSX.Element | null => {
     return null;
   }
 
-  const { grant, userRelatedGrantedGroups } = grantData;
-
   const isGrantSelectorDisabledPage = isTopPage(currentPage?.path ?? '') || isUsersProtectedPages(currentPage?.path ?? '');
 
   return (
@@ -224,12 +215,7 @@ export const SavePageControls = (): JSX.Element | null => {
             {
               isAclEnabled && (
                 <div className="me-2">
-                  <GrantSelector
-                    grant={grant}
-                    disabled={isGrantSelectorDisabledPage}
-                    userRelatedGrantedGroups={userRelatedGrantedGroups}
-                    onUpdateGrant={updateGrantHandler}
-                  />
+                  <GrantSelector disabled={isGrantSelectorDisabledPage} />
                 </div>
               )
             }
@@ -256,11 +242,8 @@ export const SavePageControls = (): JSX.Element | null => {
                   isAclEnabled && (
                     <>
                       <GrantSelector
-                        grant={grant}
                         disabled={isGrantSelectorDisabledPage}
                         openInModal
-                        userRelatedGrantedGroups={userRelatedGrantedGroups}
-                        onUpdateGrant={updateGrantHandler}
                       />
                     </>
                   )

+ 90 - 52
apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx

@@ -1,7 +1,7 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 
 import {
-  PageGrant, isPopulated, GroupType, type IGrantedGroup,
+  PageGrant, GroupType, getIdForRef,
 } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
@@ -12,10 +12,12 @@ import {
 } from 'reactstrap';
 
 import { LoadingSpinner } from '~/components/LoadingSpinner';
-import type { IPageGrantData } from '~/interfaces/page';
+import type { UserRelatedGroupsData } from '~/interfaces/page';
+import { UserGroupPageGrantStatus } from '~/interfaces/page';
 import { useCurrentUser } from '~/stores/context';
+import { useCurrentPageId, useSWRxIsGrantNormalized } from '~/stores/page';
+import { useSelectedGrant } from '~/stores/ui';
 
-import { useMyUserGroups } from './use-my-user-groups';
 
 const AVAILABLE_GRANTS = [
   {
@@ -41,14 +43,6 @@ const AVAILABLE_GRANTS = [
 type Props = {
   disabled?: boolean,
   openInModal?: boolean,
-  grant: PageGrant,
-  userRelatedGrantedGroups?: {
-    id: string,
-    name: string,
-    type: GroupType,
-  }[]
-
-  onUpdateGrant?: (grantData: IPageGrantData) => void,
 }
 
 /**
@@ -60,9 +54,6 @@ export const GrantSelector = (props: Props): JSX.Element => {
   const {
     disabled,
     openInModal,
-    userRelatedGrantedGroups,
-    onUpdateGrant,
-    grant: currentGrant,
   } = props;
 
 
@@ -71,12 +62,35 @@ export const GrantSelector = (props: Props): JSX.Element => {
   const { data: currentUser } = useCurrentUser();
 
   const shouldFetch = isSelectGroupModalShown;
-  const { data: myUserGroups, update: updateMyUserGroups } = useMyUserGroups(shouldFetch);
+  const { data: selectedGrant, mutate: mutateSelectedGrant } = useSelectedGrant();
+  const { data: currentPageId } = useCurrentPageId();
+  const { data: grantData } = useSWRxIsGrantNormalized(currentPageId);
+
+  const currentPageGrantData = grantData?.grantData.currentPageGrant;
+  const groupGrantData = currentPageGrantData?.groupGrantData;
+
+  const applyCurrentPageGrantToSelectedGrant = useCallback(() => {
+    const currentPageGrant = grantData?.grantData.currentPageGrant;
+    if (currentPageGrant == null) return;
+
+    const userRelatedGrantedGroups = currentPageGrant.groupGrantData
+      ?.userRelatedGroups.filter(group => group.status === UserGroupPageGrantStatus.isGranted)?.map((group) => {
+        return { item: group.id, type: group.type };
+      }) ?? [];
+    mutateSelectedGrant({
+      grant: currentPageGrant.grant,
+      userRelatedGrantedGroups,
+    });
+  }, [grantData?.grantData.currentPageGrant, mutateSelectedGrant]);
+
+  // sync grant data
+  useEffect(() => {
+    applyCurrentPageGrantToSelectedGrant();
+  }, [applyCurrentPageGrantToSelectedGrant]);
 
   const showSelectGroupModal = useCallback(() => {
-    updateMyUserGroups();
     setIsSelectGroupModalShown(true);
-  }, [updateMyUserGroups]);
+  }, []);
 
   /**
    * change event handler for grant selector
@@ -84,28 +98,27 @@ export const GrantSelector = (props: Props): JSX.Element => {
   const changeGrantHandler = useCallback((grant: PageGrant) => {
     // select group
     if (grant === 5) {
+      if (selectedGrant?.grant !== 5) applyCurrentPageGrantToSelectedGrant();
       showSelectGroupModal();
       return;
     }
 
-    if (onUpdateGrant != null) {
-      onUpdateGrant({ grant, userRelatedGrantedGroups: undefined });
+    mutateSelectedGrant({ grant, userRelatedGrantedGroups: undefined });
+  }, [mutateSelectedGrant, showSelectGroupModal, applyCurrentPageGrantToSelectedGrant, selectedGrant?.grant]);
+
+  const groupListItemClickHandler = useCallback((clickedGroup: UserRelatedGroupsData) => {
+    const userRelatedGrantedGroups = selectedGrant?.userRelatedGrantedGroups ?? [];
+
+    let userRelatedGrantedGroupsCopy = [...userRelatedGrantedGroups];
+    if (userRelatedGrantedGroupsCopy.find(group => getIdForRef(group.item) === clickedGroup.id) == null) {
+      const grantGroupInfo = { item: clickedGroup.id, type: clickedGroup.type };
+      userRelatedGrantedGroupsCopy.push(grantGroupInfo);
     }
-  }, [onUpdateGrant, showSelectGroupModal]);
-
-  const groupListItemClickHandler = useCallback((grantGroup: IGrantedGroup) => {
-    if (onUpdateGrant != null && isPopulated(grantGroup.item)) {
-      let userRelatedGrantedGroupsCopy = userRelatedGrantedGroups != null ? [...userRelatedGrantedGroups] : [];
-      const grantGroupInfo = { id: grantGroup.item._id, name: grantGroup.item.name, type: grantGroup.type };
-      if (userRelatedGrantedGroupsCopy.find(group => group.id === grantGroupInfo.id) == null) {
-        userRelatedGrantedGroupsCopy.push(grantGroupInfo);
-      }
-      else {
-        userRelatedGrantedGroupsCopy = userRelatedGrantedGroupsCopy.filter(group => group.id !== grantGroupInfo.id);
-      }
-      onUpdateGrant({ grant: 5, userRelatedGrantedGroups: userRelatedGrantedGroupsCopy });
+    else {
+      userRelatedGrantedGroupsCopy = userRelatedGrantedGroupsCopy.filter(group => getIdForRef(group.item) !== clickedGroup.id);
     }
-  }, [onUpdateGrant, userRelatedGrantedGroups]);
+    mutateSelectedGrant({ grant: 5, userRelatedGrantedGroups: userRelatedGrantedGroupsCopy });
+  }, [mutateSelectedGrant, selectedGrant?.userRelatedGrantedGroups]);
 
   /**
    * Render grant selector DOM.
@@ -115,8 +128,13 @@ export const GrantSelector = (props: Props): JSX.Element => {
     let dropdownToggleBtnColor;
     let dropdownToggleLabelElm;
 
+    const userRelatedGrantedGroups = groupGrantData?.userRelatedGroups.filter((group) => {
+      return selectedGrant?.userRelatedGrantedGroups?.some(grantedGroup => getIdForRef(grantedGroup.item) === group.id);
+    }) ?? [];
+    const nonUserRelatedGrantedGroups = groupGrantData?.nonUserRelatedGrantedGroups ?? [];
+
     const dropdownMenuElems = AVAILABLE_GRANTS.map((opt) => {
-      const label = ((opt.grant === 5 && opt.reselectLabel != null) && userRelatedGrantedGroups != null && userRelatedGrantedGroups.length > 0)
+      const label = ((opt.grant === 5 && opt.reselectLabel != null) && userRelatedGrantedGroups.length > 0)
         ? opt.reselectLabel // when grantGroup is selected
         : opt.label;
 
@@ -128,7 +146,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
       );
 
       // set dropdownToggleBtnColor, dropdownToggleLabelElm
-      if (opt.grant === 1 || opt.grant === currentGrant) {
+      if (opt.grant === 1 || opt.grant === selectedGrant?.grant) {
         dropdownToggleBtnColor = opt.btnStyleClass;
         dropdownToggleLabelElm = labelElm;
       }
@@ -137,18 +155,19 @@ export const GrantSelector = (props: Props): JSX.Element => {
     });
 
     // add specified group option
-    if (userRelatedGrantedGroups != null && userRelatedGrantedGroups.length > 0) {
+    if (selectedGrant?.grant === PageGrant.GRANT_USER_GROUP && (userRelatedGrantedGroups.length > 0 || nonUserRelatedGrantedGroups.length > 0)) {
+      const grantedGroupNames = [...userRelatedGrantedGroups.map(group => group.name), ...nonUserRelatedGrantedGroups.map(group => group.name)];
       const labelElm = (
         <span>
           <span className="material-symbols-outlined me-1">account_tree</span>
           <span className="label">
-            {userRelatedGrantedGroups.length > 1
+            {grantedGroupNames.length > 1
               ? (
                 <span>
-                  {`${userRelatedGrantedGroups[0].name}... `}
-                  <span className="badge badge-purple">+{userRelatedGrantedGroups.length - 1}</span>
+                  {`${grantedGroupNames[0]}... `}
+                  <span className="badge badge-purple">+{grantedGroupNames.length - 1}</span>
                 </span>
-              ) : userRelatedGrantedGroups[0].name}
+              ) : grantedGroupNames[0]}
           </span>
         </span>
       );
@@ -171,7 +190,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
         </UncontrolledDropdown>
       </div>
     );
-  }, [changeGrantHandler, currentGrant, disabled, userRelatedGrantedGroups, t, openInModal]);
+  }, [changeGrantHandler, disabled, groupGrantData, selectedGrant, t, openInModal]);
 
   /**
    * Render select grantgroup modal.
@@ -182,7 +201,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
     }
 
     // show spinner
-    if (myUserGroups == null) {
+    if (groupGrantData == null) {
       return (
         <div className="my-3 text-center">
           <LoadingSpinner className="mx-auto text-muted fs-4" />
@@ -190,7 +209,9 @@ export const GrantSelector = (props: Props): JSX.Element => {
       );
     }
 
-    if (myUserGroups.length === 0) {
+    const { userRelatedGroups, nonUserRelatedGrantedGroups } = groupGrantData;
+
+    if (userRelatedGroups.length === 0) {
       return (
         <div>
           <h4>{t('user_group.belonging_to_no_group')}</h4>
@@ -203,20 +224,37 @@ export const GrantSelector = (props: Props): JSX.Element => {
 
     return (
       <div className="d-flex flex-column">
-        { myUserGroups.map((group) => {
-          const groupIsGranted = userRelatedGrantedGroups?.find(g => g.id === group.item._id) != null;
-          const activeClass = groupIsGranted ? 'active' : '';
+        { userRelatedGroups.map((group) => {
+          const isGroupGranted = selectedGrant?.userRelatedGrantedGroups?.some(grantedGroup => getIdForRef(grantedGroup.item) === group.id);
+          const cannotGrantGroup = group.status === UserGroupPageGrantStatus.cannotGrant;
+          const activeClass = isGroupGranted ? 'active' : '';
 
           return (
             <button
               className={`btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 ${activeClass}`}
               type="button"
-              key={group.item._id}
+              key={group.id}
               onClick={() => groupListItemClickHandler(group)}
+              disabled={cannotGrantGroup}
+            >
+              <span className="align-middle"><input type="checkbox" checked={isGroupGranted} disabled={cannotGrantGroup} /></span>
+              <h5 className="d-inline-block ms-3">{group.name}</h5>
+              {group.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{group.provider}</span>}
+              {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
+            </button>
+          );
+        }) }
+        { nonUserRelatedGrantedGroups.map((group) => {
+          return (
+            <button
+              className="btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 active"
+              type="button"
+              key={group.id}
+              disabled
             >
-              <span className="align-middle"><input type="checkbox" checked={groupIsGranted} /></span>
-              <h5 className="d-inline-block ms-3">{group.item.name}</h5>
-              {group.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{group.item.provider}</span>}
+              <span className="align-middle"><input type="checkbox" checked disabled /></span>
+              <h5 className="d-inline-block ms-3">{group.name}</h5>
+              {group.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{group.provider}</span>}
               {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
             </button>
           );
@@ -225,7 +263,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
       </div>
     );
 
-  }, [currentUser?.admin, groupListItemClickHandler, myUserGroups, shouldFetch, t, userRelatedGrantedGroups]);
+  }, [currentUser?.admin, groupListItemClickHandler, shouldFetch, t, groupGrantData, selectedGrant?.userRelatedGrantedGroups]);
 
   return (
     <>

+ 0 - 38
apps/app/src/components/SavePageControls/GrantSelector/use-my-user-groups.ts

@@ -1,38 +0,0 @@
-import { GroupType } from '@growi/core';
-
-import { useSWRxMyExternalUserGroups } from '~/features/external-user-group/client/stores/external-user-group';
-import { useSWRxMyUserGroups } from '~/stores/user-group';
-
-// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-export const useMyUserGroups = (shouldFetch: boolean) => {
-  const { data: myUserGroups, mutate: mutateMyUserGroups } = useSWRxMyUserGroups(shouldFetch);
-  const { data: myExternalUserGroups, mutate: mutateMyExternalUserGroups } = useSWRxMyExternalUserGroups(shouldFetch);
-
-  const update = () => {
-    mutateMyUserGroups();
-    mutateMyExternalUserGroups();
-  };
-
-  if (myUserGroups == null || myExternalUserGroups == null) {
-    return { data: null, update };
-  }
-
-  const myUserGroupsData = myUserGroups
-    .map((group) => {
-      return {
-        item: group,
-        type: GroupType.userGroup,
-      };
-    });
-  const myExternalUserGroupsData = myExternalUserGroups
-    .map((group) => {
-      return {
-        item: group,
-        type: GroupType.externalUserGroup,
-      };
-    });
-
-  const data = [...myUserGroupsData, ...myExternalUserGroupsData];
-
-  return { data, update };
-};

+ 4 - 12
apps/app/src/features/external-user-group/client/stores/external-user-group.ts

@@ -1,14 +1,13 @@
 import { type SWRResponseWithUtils, withUtils } from '@growi/core/dist/swr';
-import useSWR, { SWRResponse } from 'swr';
+import type { SWRResponse } from 'swr';
+import useSWR from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
-import {
+import type {
   IExternalUserGroupHasId, IExternalUserGroupRelationHasId, KeycloakGroupSyncSettings, LdapGroupSyncSettings,
 } from '~/features/external-user-group/interfaces/external-user-group';
-import {
-  ChildUserGroupListResult, IUserGroupRelationHasIdPopulatedUser, UserGroupListResult, UserGroupRelationListResult,
-} from '~/interfaces/user-group-response';
+import type { ChildUserGroupListResult, IUserGroupRelationHasIdPopulatedUser, UserGroupRelationListResult } from '~/interfaces/user-group-response';
 
 export const useSWRxLdapGroupSyncSettings = (): SWRResponse<LdapGroupSyncSettings, Error> => {
   return useSWR(
@@ -28,13 +27,6 @@ export const useSWRxKeycloakGroupSyncSettings = (): SWRResponse<KeycloakGroupSyn
   );
 };
 
-export const useSWRxMyExternalUserGroups = (shouldFetch: boolean): SWRResponse<IExternalUserGroupHasId[], Error> => {
-  return useSWR(
-    shouldFetch ? '/me/external-user-groups' : null,
-    endpoint => apiv3Get<UserGroupListResult<IExternalUserGroupHasId>>(endpoint).then(result => result.data.userGroups),
-  );
-};
-
 export const useSWRxExternalUserGroup = (groupId: string | null): SWRResponse<IExternalUserGroupHasId, Error> => {
   return useSWRImmutable(
     groupId != null ? `/external-user-groups/${groupId}` : null,

+ 3 - 44
apps/app/src/pages/[[...path]].page.tsx

@@ -3,9 +3,8 @@ import React, { useEffect } from 'react';
 
 import EventEmitter from 'events';
 
-import { isIPageInfoForEntity, isPopulated } from '@growi/core';
+import { isIPageInfoForEntity } from '@growi/core';
 import type {
-  GroupType,
   IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision,
 } from '@growi/core';
 import {
@@ -26,7 +25,6 @@ import { PageView } from '~/components/Page/PageView';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { SupportedAction, type SupportedActionType } from '~/interfaces/activity';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
-import type { IPageGrantData } from '~/interfaces/page';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { PageModel, PageDocument } from '~/server/models/page';
 import type { PageRedirectModel } from '~/server/models/page-redirect';
@@ -44,7 +42,7 @@ import {
 } from '~/stores/context';
 import { useEditingMarkdown } from '~/stores/editor';
 import {
-  useSWRxCurrentPage, useSWRMUTxCurrentPage, useSWRxIsGrantNormalized, useCurrentPageId,
+  useSWRxCurrentPage, useSWRMUTxCurrentPage, useCurrentPageId,
   useIsNotFound, useIsLatestRevision, useTemplateTagData, useTemplateBodyData,
 } from '~/stores/page';
 import { useRedirectFrom } from '~/stores/page-redirect';
@@ -172,8 +170,6 @@ type Props = CommonProps & {
   skipSSR: boolean,
   ssrMaxRevisionBodyLength: number,
 
-  grantData?: IPageGrantData,
-
   rendererConfig: RendererConfig,
 };
 
@@ -241,9 +237,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
   const { mutate: mutateIsLatestRevision } = useIsLatestRevision();
 
-  const { data: grantData } = useSWRxIsGrantNormalized(pageId);
-  const { mutate: mutateSelectedGrant } = useSelectedGrant();
-
   const { mutate: mutateRemoteRevisionId } = useRemoteRevisionId();
 
   const { mutate: mutateTemplateTagData } = useTemplateTagData();
@@ -270,12 +263,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
     }
   }, [currentPageId, mutateCurrentPage, mutateEditingMarkdown, props.isNotFound, props.skipSSR]);
 
-  // sync grant data
-  useEffect(() => {
-    const grantDataToApply = props.grantData ? props.grantData : grantData?.grantData.currentPageGrant;
-    mutateSelectedGrant(grantDataToApply);
-  }, [grantData?.grantData.currentPageGrant, mutateSelectedGrant, props.grantData]);
-
   // sync pathname by Shallow Routing https://nextjs.org/docs/routing/shallow-routing
   useEffect(() => {
     const decodedURI = decodeURI(window.location.pathname);
@@ -413,7 +400,7 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
 
   const Page = crowi.model('Page') as PageModel;
   const PageRedirect = mongooseModel('PageRedirect') as PageRedirectModel;
-  const { pageService, configManager, pageGrantService } = crowi;
+  const { pageService, configManager } = crowi;
 
   let currentPathname = props.currentPathname;
 
@@ -457,34 +444,6 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
     await page.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
   }
 
-  if (page == null && user != null) {
-    const templateData = await Page.findTemplate(props.currentPathname);
-    if (templateData != null) {
-      props.templateTagData = templateData.templateTags as string[];
-      props.templateBodyData = templateData.templateBody as string;
-    }
-
-    // apply parent page grant, without groups that user isn't related to
-    const ancestor = await Page.findAncestorByPathAndViewer(currentPathname, user);
-    if (ancestor != null) {
-      ancestor.populate('grantedGroups.item');
-      const userRelatedGrantedGroups = (await pageGrantService.getUserRelatedGrantedGroups(ancestor, user)).map((group) => {
-        if (isPopulated(group.item)) {
-          return {
-            id: group.item._id,
-            name: group.item.name,
-            type: group.type,
-          };
-        }
-        return null;
-      }).filter((info): info is NonNullable<{id: string, name: string, type: GroupType}> => info != null);
-      props.grantData = {
-        grant: ancestor.grant,
-        userRelatedGrantedGroups,
-      };
-    }
-  }
-
   props.pageWithMeta = pageWithMeta;
 }
 

+ 0 - 2
apps/app/src/server/routes/apiv3/index.js

@@ -118,7 +118,5 @@ module.exports = (crowi, app) => {
   router.use('/questionnaire', require('~/features/questionnaire/server/routes/apiv3/questionnaire')(crowi));
   router.use('/templates', require('~/features/templates/server/routes/apiv3')(crowi));
 
-  router.use('/me', require('./me')(crowi));
-
   return [router, routerForAdmin, routerForAuth];
 };

+ 0 - 54
apps/app/src/server/routes/apiv3/me.ts

@@ -1,54 +0,0 @@
-import { type IUserHasId } from '@growi/core';
-import { Router, Request } from 'express';
-
-import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
-import loggerFactory from '~/utils/logger';
-
-import UserGroupRelation from '../../models/user-group-relation';
-
-import { ApiV3Response } from './interfaces/apiv3-response';
-
-const logger = loggerFactory('growi:routes:apiv3:me');
-
-const router = Router();
-
-interface AuthorizedRequest extends Request {
-  user?: IUserHasId
-}
-
-module.exports = function(crowi) {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
-  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
-
-  const ApiResponse = require('../../util/apiResponse');
-
-  /**
-   * retrieve user-group documents
-   */
-  router.get('/user-groups', accessTokenParser, loginRequiredStrictly, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    try {
-      const userGroups = await UserGroupRelation.findAllGroupsForUser(req.user);
-      return res.json(ApiResponse.success({ userGroups }));
-    }
-    catch (e) {
-      logger.error(e);
-      return res.apiv3Err(e, 500);
-    }
-  });
-
-  /**
-   * retrieve external-user-group-relation documents
-   */
-  router.get('/external-user-groups', accessTokenParser, loginRequiredStrictly, async(req: AuthorizedRequest, res: ApiV3Response) => {
-    try {
-      const userGroups = await ExternalUserGroupRelation.findAllGroupsForUser(req.user);
-      return res.json(ApiResponse.success({ userGroups }));
-    }
-    catch (e) {
-      logger.error(e);
-      return res.apiv3Err(e, 500);
-    }
-  });
-
-  return router;
-};

+ 3 - 3
apps/app/src/stores/ui.tsx

@@ -17,7 +17,7 @@ import useSWRImmutable from 'swr/immutable';
 
 import type { IFocusable } from '~/client/interfaces/focusable';
 import { scheduleToPut } from '~/client/services/user-ui-settings';
-import type { IPageGrantData } from '~/interfaces/page';
+import type { IPageGrantData, IPageSelectedGrant } from '~/interfaces/page';
 import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
 import type { UpdateDescCountData } from '~/interfaces/websocket';
 import {
@@ -348,8 +348,8 @@ export const useSidebarMode = (): SWRResponseWithUtils<DetectSidebarModeUtils, S
   };
 };
 
-export const useSelectedGrant = (initialData?: Nullable<IPageGrantData>): SWRResponse<Nullable<IPageGrantData>, Error> => {
-  return useStaticSWR<Nullable<IPageGrantData>, Error>('selectedGrant', initialData, { fallbackData: { grant: PageGrant.GRANT_PUBLIC } });
+export const useSelectedGrant = (initialData?: Nullable<IPageSelectedGrant>): SWRResponse<Nullable<IPageSelectedGrant>, Error> => {
+  return useSWRStatic<Nullable<IPageSelectedGrant>, Error>('selectedGrant', initialData, { fallbackData: { grant: PageGrant.GRANT_PUBLIC } });
 };
 
 type PageTreeDescCountMapUtils = {

+ 2 - 10
apps/app/src/stores/user-group.tsx

@@ -2,24 +2,16 @@ import type {
   IPageHasId, IUserGroupHasId, IUserGroupRelationHasId,
 } from '@growi/core';
 import { type SWRResponseWithUtils, withUtils } from '@growi/core/dist/swr';
-import useSWR, { SWRResponse } from 'swr';
+import type { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
-import {
+import type {
   IUserGroupRelationHasIdPopulatedUser,
   UserGroupResult, UserGroupListResult, ChildUserGroupListResult, UserGroupRelationListResult, UserGroupRelationsResult,
   UserGroupPagesResult, SelectableParentUserGroupsResult, SelectableUserChildGroupsResult, AncestorUserGroupsResult,
 } from '~/interfaces/user-group-response';
 
-
-export const useSWRxMyUserGroups = (shouldFetch: boolean): SWRResponse<IUserGroupHasId[], Error> => {
-  return useSWR(
-    shouldFetch ? '/me/user-groups' : null,
-    endpoint => apiv3Get<UserGroupListResult>(endpoint).then(result => result.data.userGroups),
-  );
-};
-
 export const useSWRxUserGroup = (groupId: string | null): SWRResponse<IUserGroupHasId, Error> => {
   return useSWRImmutable(
     groupId != null ? `/user-groups/${groupId}` : null,