Kaynağa Gözat

Merge pull request #7605 from weseek/feat/86710-118145-get-attachment-data

feat: Create useSWRxAttachment
Yuki Takei 2 yıl önce
ebeveyn
işleme
f5da6a1134

+ 3 - 14
apps/app/src/components/ReactMarkdownComponents/Attachment.tsx

@@ -1,29 +1,18 @@
-import React, { useMemo, useCallback } from 'react';
+import React, { useCallback } from 'react';
 
 
 import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
 import prettyBytes from 'pretty-bytes';
 import prettyBytes from 'pretty-bytes';
 
 
-import { useSWRxAttachments } from '~/stores/attachment';
+import { useSWRxAttachment } from '~/stores/attachment';
 import { useAttachmentDeleteModal } from '~/stores/modal';
 import { useAttachmentDeleteModal } from '~/stores/modal';
-import { useCurrentPageId } from '~/stores/page';
 
 
 export const Attachment: React.FC<{
 export const Attachment: React.FC<{
   attachmentId: string,
   attachmentId: string,
   url: string,
   url: string,
   attachmentName: string
   attachmentName: string
 }> = React.memo(({ attachmentId, url, attachmentName }) => {
 }> = React.memo(({ attachmentId, url, attachmentName }) => {
-  const { data: pageId } = useCurrentPageId();
-  // TODO: We need to be able to get it from all pages if there are a lot of attachments.
-  const { data: dataAttachments, remove } = useSWRxAttachments(pageId, 1);
+  const { data: attachment, remove } = useSWRxAttachment(attachmentId);
   const { open: openAttachmentDeleteModal } = useAttachmentDeleteModal();
   const { open: openAttachmentDeleteModal } = useAttachmentDeleteModal();
-
-  const attachment = useMemo(() => {
-    if (dataAttachments == null) {
-      return;
-    }
-    return dataAttachments.attachments.find(item => item._id === attachmentId);
-  }, [attachmentId, dataAttachments]);
-
   const onClickTrashButtonHandler = useCallback(() => {
   const onClickTrashButtonHandler = useCallback(() => {
     if (attachment == null) {
     if (attachment == null) {
       return;
       return;

+ 36 - 0
apps/app/src/server/routes/apiv3/attachment.js

@@ -26,12 +26,48 @@ module.exports = (crowi) => {
   const Attachment = crowi.model('Attachment');
   const Attachment = crowi.model('Attachment');
 
 
   const validator = {
   const validator = {
+    attachment: [
+      query('attachmentId').isMongoId().withMessage('attachmentId is required'),
+    ],
     retrieveAttachments: [
     retrieveAttachments: [
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageId').isMongoId().withMessage('pageId is required'),
       query('pageNumber').optional().isInt().withMessage('pageNumber must be a number'),
       query('pageNumber').optional().isInt().withMessage('pageNumber must be a number'),
       query('limit').optional().isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'),
       query('limit').optional().isInt({ max: 100 }).withMessage('You should set less than 100 or not to set limit.'),
     ],
     ],
   };
   };
+
+  /**
+   * @swagger
+   *
+   *    /attachment:
+   *      get:
+   *        tags: [Attachment]
+   *        description: Get attachment
+   *        responses:
+   *          200:
+   *            description: Return attachment
+   *        parameters:
+   *          - name: attachemnt_id
+   *            in: query
+   *            required: true
+   *            description: attachment id
+   *            schema:
+   *              type: string
+   */
+  router.get('/', accessTokenParser, loginRequired, validator.attachment, apiV3FormValidator, async(req, res) => {
+    try {
+      const attachmentId = req.query.attachmentId;
+
+      const attachment = await Attachment.findById(attachmentId);
+
+      return res.apiv3({ attachment });
+    }
+    catch (err) {
+      logger.error('Attachment not found', err);
+      return res.apiv3Err(err, 500);
+    }
+  });
+
   /**
   /**
    * @swagger
    * @swagger
    *
    *

+ 29 - 3
apps/app/src/stores/attachment.tsx

@@ -1,9 +1,9 @@
 import { useCallback } from 'react';
 import { useCallback } from 'react';
 
 
 import {
 import {
-  HasObjectId,
-  IAttachment, Nullable, type SWRResponseWithUtils, withUtils,
+  IAttachmentHasId, Nullable, type SWRResponseWithUtils, withUtils,
 } from '@growi/core';
 } from '@growi/core';
+import { Util } from 'reactstrap';
 import useSWR from 'swr';
 import useSWR from 'swr';
 
 
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
@@ -15,11 +15,37 @@ type Util = {
 };
 };
 
 
 type IDataAttachmentList = {
 type IDataAttachmentList = {
-  attachments: (IAttachment & HasObjectId)[]
+  attachments: (IAttachmentHasId)[]
   totalAttachments: number
   totalAttachments: number
   limit: number
   limit: number
 };
 };
 
 
+export const useSWRxAttachment = (attachmentId: string): SWRResponseWithUtils<Util, IAttachmentHasId, Error> => {
+  const swrResponse = useSWR(
+    ['/attachment', attachmentId],
+    useCallback(async([endpoint, attachmentId]) => {
+      const params = {
+        attachmentId,
+      };
+      const res = await apiv3Get(endpoint, params);
+      return res.data.attachment;
+    }, []),
+  );
+
+  // Utils
+  const remove = useCallback(async(body: { attachment_id: string }) => {
+    try {
+      await apiPost('/attachments.remove', body);
+      swrResponse.mutate();
+    }
+    catch (err) {
+      throw err;
+    }
+  }, [swrResponse]);
+
+  return withUtils<Util, IAttachmentHasId, Error>(swrResponse, { remove });
+};
+
 export const useSWRxAttachments = (pageId?: Nullable<string>, pageNumber?: number): SWRResponseWithUtils<Util, IDataAttachmentList, Error> => {
 export const useSWRxAttachments = (pageId?: Nullable<string>, pageNumber?: number): SWRResponseWithUtils<Util, IDataAttachmentList, Error> => {
   const shouldFetch = pageId != null && pageNumber != null;
   const shouldFetch = pageId != null && pageNumber != null;
 
 

+ 4 - 4
apps/app/src/stores/modal.tsx

@@ -1,6 +1,6 @@
 import { useCallback, useMemo } from 'react';
 import { useCallback, useMemo } from 'react';
 
 
-import type { HasObjectId, IAttachment } from '@growi/core';
+import type { IAttachmentHasId } from '@growi/core';
 import { SWRResponse } from 'swr';
 import { SWRResponse } from 'swr';
 
 
 import MarkdownTable from '~/client/models/MarkdownTable';
 import MarkdownTable from '~/client/models/MarkdownTable';
@@ -619,13 +619,13 @@ type Remove =
 
 
 type AttachmentDeleteModalStatus = {
 type AttachmentDeleteModalStatus = {
   isOpened: boolean,
   isOpened: boolean,
-  attachment?: IAttachment & HasObjectId,
+  attachment?: IAttachmentHasId,
   remove?: Remove,
   remove?: Remove,
 }
 }
 
 
 type AttachmentDeleteModalUtils = {
 type AttachmentDeleteModalUtils = {
   open(
   open(
-    attachment: IAttachment & HasObjectId,
+    attachment: IAttachmentHasId,
     remove: Remove,
     remove: Remove,
   ): void,
   ): void,
   close(): void,
   close(): void,
@@ -640,7 +640,7 @@ export const useAttachmentDeleteModal = (): SWRResponse<AttachmentDeleteModalSta
   const swrResponse = useStaticSWR<AttachmentDeleteModalStatus, Error>('attachmentDeleteModal', undefined, { fallbackData: initialStatus });
   const swrResponse = useStaticSWR<AttachmentDeleteModalStatus, Error>('attachmentDeleteModal', undefined, { fallbackData: initialStatus });
   const { mutate } = swrResponse;
   const { mutate } = swrResponse;
 
 
-  const open = useCallback((attachment: IAttachment & HasObjectId, remove: Remove) => {
+  const open = useCallback((attachment: IAttachmentHasId, remove: Remove) => {
     mutate({ isOpened: true, attachment, remove });
     mutate({ isOpened: true, attachment, remove });
   }, [mutate]);
   }, [mutate]);
   const close = useCallback((): void => {
   const close = useCallback((): void => {