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

Merge pull request #8467 from weseek/imprv/create-page-with-grant-by-parent-page

fix: Check page existence
Yuki Takei 2 лет назад
Родитель
Сommit
0114cc8a2a

+ 2 - 3
apps/app/src/client/services/create-page/use-create-page-and-transit.tsx

@@ -61,10 +61,9 @@ export const useCreatePageAndTransit: UseCreatePageAndTransit = () => {
       const pagePath = params.path;
 
       try {
-        const res = await exist(JSON.stringify([pagePath]));
-        const isExists = res.pages[pagePath];
+        const { isExist } = await exist(pagePath);
 
-        if (isExists) {
+        if (isExist) {
           // routing
           if (pagePath !== currentPagePath) {
             await router.push(`${pagePath}#edit`);

+ 6 - 12
apps/app/src/client/services/page-operation.ts

@@ -11,8 +11,8 @@ import { useCurrentPageId, useSWRMUTxCurrentPage, useSWRxTagsInfo } from '~/stor
 import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
-import { apiGet, apiPost } from '../util/apiv1-client';
-import { apiv3Post, apiv3Put } from '../util/apiv3-client';
+import { apiPost } from '../util/apiv1-client';
+import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { toastError } from '../util/toastr';
 
 const logger = loggerFactory('growi:services:page-operation');
@@ -151,17 +151,11 @@ export const unlink = async(path: string): Promise<void> => {
 };
 
 
-interface PageExistRequest {
-  pagePaths: string;
-}
-
 interface PageExistResponse {
-  pages: Record<string, boolean>;
-  ok: boolean
+  isExist: boolean,
 }
 
-export const exist = async(pagePaths: string): Promise<PageExistResponse> => {
-  const request: PageExistRequest = { pagePaths };
-  const res = await apiGet<PageExistResponse>('/pages.exist', request);
-  return res;
+export const exist = async(path: string): Promise<PageExistResponse> => {
+  const res = await apiv3Get<PageExistResponse>('/page/exist', { path });
+  return res.data;
 };

+ 3 - 3
apps/app/src/components/CreateTemplateModal.tsx

@@ -53,7 +53,7 @@ type CreateTemplateModalProps = {
 export const CreateTemplateModal: React.FC<CreateTemplateModalProps> = ({
   path, isOpen, onClose,
 }) => {
-  const { t } = useTranslation();
+  const { t } = useTranslation(['translation', 'commons']);
 
   const { createTemplate, isCreating, isCreatable } = useCreateTemplatePage();
 
@@ -62,9 +62,9 @@ export const CreateTemplateModal: React.FC<CreateTemplateModalProps> = ({
       await createTemplate?.(label);
     }
     catch (err) {
-      toastError(err);
+      toastError(t('toaster.create_failed', { target: path }));
     }
-  }, [createTemplate]);
+  }, [createTemplate, path, t]);
 
   const parentPath = pathUtils.addTrailingSlash(path);
 

+ 3 - 3
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -56,7 +56,7 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
     path,
   } = props;
 
-  const { t } = useTranslation('common');
+  const { t } = useTranslation('commons');
 
   const { data: isNotFound } = useIsNotFound();
   const { mutate: mutateEditorMode } = useEditorMode();
@@ -64,14 +64,14 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
 
   const { isCreating, createAndTransit } = useCreatePageAndTransit();
 
-  const editButtonClickedHandler = useCallback(() => {
+  const editButtonClickedHandler = useCallback(async() => {
     if (isNotFound == null || isNotFound === false) {
       mutateEditorMode(EditorMode.Editor);
       return;
     }
 
     try {
-      createAndTransit(
+      await createAndTransit(
         { path },
         { shouldCheckPageExists: true },
       );

+ 5 - 2
apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx

@@ -1,5 +1,6 @@
 import React, { useState, useCallback } from 'react';
 
+import { useTranslation } from 'react-i18next';
 import { Dropdown } from 'reactstrap';
 
 import { useCreateTemplatePage } from '~/client/services/create-page';
@@ -12,14 +13,16 @@ import { useCreateNewPage, useCreateTodaysMemo } from './hooks';
 
 
 const useToastrOnError = <P, R>(method?: (param?: P) => Promise<R|undefined>): (param?: P) => Promise<R|undefined> => {
+  const { t } = useTranslation('commons');
+
   return useCallback(async(param) => {
     try {
       return await method?.(param);
     }
     catch (err) {
-      toastError(err);
+      toastError(t('toaster.create_failed', { target: 'a page' }));
     }
-  }, [method]);
+  }, [method, t]);
 };
 
 

+ 54 - 0
apps/app/src/server/routes/apiv3/page/check-page-existence.ts

@@ -0,0 +1,54 @@
+import type { IPage, IUserHasId } from '@growi/core';
+import { ErrorV3 } from '@growi/core/dist/models';
+import type { Request, RequestHandler } from 'express';
+import type { ValidationChain } from 'express-validator';
+import { query } from 'express-validator';
+import mongoose from 'mongoose';
+
+import type Crowi from '~/server/crowi';
+import type { PageModel } from '~/server/models/page';
+import loggerFactory from '~/utils/logger';
+
+import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
+import type { ApiV3Response } from '../interfaces/apiv3-response';
+
+
+const logger = loggerFactory('growi:routes:apiv3:page:check-page-existence');
+
+
+type ReqQuery = {
+  path: string,
+}
+
+interface Req extends Request<ReqQuery, ApiV3Response> {
+  user: IUserHasId,
+}
+
+type CreatePageHandlersFactory = (crowi: Crowi) => RequestHandler[];
+
+export const checkPageExistenceHandlersFactory: CreatePageHandlersFactory = (crowi) => {
+  const Page = mongoose.model<IPage, PageModel>('Page');
+
+  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
+  const loginRequired = require('../../../middlewares/login-required')(crowi, true);
+
+  // define validators for req.body
+  const validator: ValidationChain[] = [
+    query('path').isString().withMessage('The param "path" must be specified'),
+  ];
+
+  return [
+    accessTokenParser, loginRequired,
+    validator, apiV3FormValidator,
+    async(req: Req, res: ApiV3Response) => {
+      const { path } = req.query;
+
+      if (path == null || Array.isArray(path)) {
+        return res.apiv3Err(new ErrorV3('The param "path" must be an page id'));
+      }
+
+      const count = await Page.countByPathAndViewer(path.toString(), req.user);
+      res.apiv3({ isExist: count > 0 });
+    },
+  ];
+};

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

@@ -20,6 +20,7 @@ import { preNotifyService } from '~/server/service/pre-notify';
 import { divideByType } from '~/server/util/granted-group';
 import loggerFactory from '~/utils/logger';
 
+import { checkPageExistenceHandlersFactory } from './check-page-existence';
 import { createPageHandlersFactory } from './create-page';
 import { updatePageHandlersFactory } from './update-page';
 
@@ -314,6 +315,8 @@ module.exports = (crowi) => {
     return res.apiv3({ page, pages });
   });
 
+  router.get('/exist', checkPageExistenceHandlersFactory(crowi));
+
   /**
    * @swagger
    *

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

@@ -121,7 +121,6 @@ module.exports = function(crowi, app) {
   apiV1Router.get('/search'                        , accessTokenParser , loginRequired , search.api.search);
 
   // HTTP RPC Styled API (に徐々に移行していいこうと思う)
-  apiV1Router.get('/pages.exist'         , accessTokenParser , loginRequired , page.api.exist);
   apiV1Router.get('/pages.updatePost'    , accessTokenParser, loginRequired, page.api.getUpdatePost);
   apiV1Router.get('/pages.getPageTag'    , accessTokenParser , loginRequired , page.api.getPageTag);
   // allow posting to guests because the client doesn't know whether the user logged in

+ 0 - 57
apps/app/src/server/routes/page.js

@@ -217,63 +217,6 @@ module.exports = function(crowi, app) {
   actions.api = api;
   actions.validator = validator;
 
-  /**
-   * @swagger
-   *
-   *    /pages.exist:
-   *      get:
-   *        tags: [Pages]
-   *        operationId: getPageExistence
-   *        summary: /pages.exist
-   *        description: Get page existence
-   *        parameters:
-   *          - in: query
-   *            name: pagePaths
-   *            schema:
-   *              type: string
-   *              description: Page path list in JSON Array format
-   *              example: '["/", "/user/unknown"]'
-   *        responses:
-   *          200:
-   *            description: Succeeded to get page existence.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    pages:
-   *                      type: string
-   *                      description: Properties of page path and existence
-   *                      example: '{"/": true, "/user/unknown": false}'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * @api {get} /pages.exist Get if page exists
-   * @apiName GetPage
-   * @apiGroup Page
-   *
-   * @apiParam {String} pages (stringified JSON)
-   */
-  api.exist = async function(req, res) {
-    const pagePaths = JSON.parse(req.query.pagePaths || '[]');
-
-    const pages = {};
-    await Promise.all(pagePaths.map(async(path) => {
-      // check page existence
-      const isExist = await Page.count({ path }) > 0;
-      pages[path] = isExist;
-      return;
-    }));
-
-    const result = { pages };
-
-    return res.json(ApiResponse.success(result));
-  };
-
   /**
    * @swagger
    *