From 4461b61956817cdab8e7bdec5279a1c439c42d74 Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 4 Apr 2024 20:45:58 +0000 Subject: [PATCH 1/2] re-organize interfaces for attachments --- apps/app/src/interfaces/attachment.ts | 5 +++++ .../src/server/service/file-uploader/file-uploader.ts | 10 +++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/app/src/interfaces/attachment.ts b/apps/app/src/interfaces/attachment.ts index 77ea5a2448a..fbda43f45b4 100644 --- a/apps/app/src/interfaces/attachment.ts +++ b/apps/app/src/interfaces/attachment.ts @@ -6,3 +6,8 @@ import type { PaginateResult } from './mongoose-utils'; export type IResAttachmentList = { paginateResult: PaginateResult }; + +export type ICheckLimitResult = { + isUploadable: boolean, + errorMessage?: string, +} diff --git a/apps/app/src/server/service/file-uploader/file-uploader.ts b/apps/app/src/server/service/file-uploader/file-uploader.ts index a6f00c76a11..2a97075009b 100644 --- a/apps/app/src/server/service/file-uploader/file-uploader.ts +++ b/apps/app/src/server/service/file-uploader/file-uploader.ts @@ -2,6 +2,7 @@ import { randomUUID } from 'crypto'; import type { Response } from 'express'; +import type { ICheckLimitResult } from '~/interfaces/attachment'; import { type RespondOptions, ResponseMode } from '~/server/interfaces/attachment'; import { Attachment, type IAttachmentDocument } from '~/server/models'; import loggerFactory from '~/utils/logger'; @@ -17,11 +18,6 @@ export type SaveFileParam = { data, } -export type CheckLimitResult = { - isUploadable: boolean, - errorMessage?: string, -} - export type TemporaryUrl = { url: string, lifetimeSec: number, @@ -38,7 +34,7 @@ export interface FileUploader { deleteFiles(): void, getFileUploadTotalLimit(): number, getTotalFileSize(): Promise, - doCheckLimit(uploadFileSize: number, maxFileSize: number, totalLimit: number): Promise, + doCheckLimit(uploadFileSize: number, maxFileSize: number, totalLimit: number): Promise, determineResponseMode(): ResponseMode, respond(res: Response, attachment: IAttachmentDocument, opts?: RespondOptions): void, findDeliveryFile(attachment: IAttachmentDocument): Promise, @@ -135,7 +131,7 @@ export abstract class AbstractFileUploader implements FileUploader { * Check files size limits for all uploaders * */ - async doCheckLimit(uploadFileSize: number, maxFileSize: number, totalLimit: number): Promise { + async doCheckLimit(uploadFileSize: number, maxFileSize: number, totalLimit: number): Promise { if (uploadFileSize > maxFileSize) { return { isUploadable: false, errorMessage: 'File size exceeds the size limit per file' }; } From 6cd82b6eaf3a4b34f91390824dbaf3cab79a02dd Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 4 Apr 2024 20:45:58 +0000 Subject: [PATCH 2/2] commonize uploadAttachments --- .../services/upload-attachments/index.ts | 1 + .../upload-attachments/upload-attachments.ts | 39 ++++++++++++++++ .../components/PageComment/CommentEditor.tsx | 41 +++++------------ .../src/components/PageEditor/PageEditor.tsx | 46 +++++++------------ apps/app/src/interfaces/apiv3/attachment.ts | 15 ++++++ 5 files changed, 83 insertions(+), 59 deletions(-) create mode 100644 apps/app/src/client/services/upload-attachments/index.ts create mode 100644 apps/app/src/client/services/upload-attachments/upload-attachments.ts create mode 100644 apps/app/src/interfaces/apiv3/attachment.ts diff --git a/apps/app/src/client/services/upload-attachments/index.ts b/apps/app/src/client/services/upload-attachments/index.ts new file mode 100644 index 00000000000..4bae0aea466 --- /dev/null +++ b/apps/app/src/client/services/upload-attachments/index.ts @@ -0,0 +1 @@ +export * from './upload-attachments'; diff --git a/apps/app/src/client/services/upload-attachments/upload-attachments.ts b/apps/app/src/client/services/upload-attachments/upload-attachments.ts new file mode 100644 index 00000000000..7ff17d73610 --- /dev/null +++ b/apps/app/src/client/services/upload-attachments/upload-attachments.ts @@ -0,0 +1,39 @@ +import type { IAttachment } from '@growi/core'; + +import { apiv3Get, apiv3PostForm } from '~/client/util/apiv3-client'; +import type { IApiv3GetAttachmentLimitParams, IApiv3GetAttachmentLimitResponse, IApiv3PostAttachmentResponse } from '~/interfaces/apiv3/attachment'; +import loggerFactory from '~/utils/logger'; + + +const logger = loggerFactory('growi:client:services:upload-attachment'); + + +type UploadOpts = { + onUploaded?: (attachment: IAttachment) => void, + onError?: (error: Error, file: File) => void, +} + +export const uploadAttachments = async(pageId: string, files: File[], opts?: UploadOpts): Promise => { + files.forEach(async(file) => { + try { + const params: IApiv3GetAttachmentLimitParams = { fileSize: file.size }; + const { data: resLimit } = await apiv3Get('/attachment/limit', params); + + if (!resLimit.isUploadable) { + throw new Error(resLimit.errorMessage); + } + + const formData = new FormData(); + formData.append('file', file); + formData.append('page_id', pageId); + + const { data: resAdd } = await apiv3PostForm('/attachment', formData); + + opts?.onUploaded?.(resAdd.attachment); + } + catch (e) { + logger.error('failed to upload', e); + opts?.onError?.(e, file); + } + }); +}; diff --git a/apps/app/src/components/PageComment/CommentEditor.tsx b/apps/app/src/components/PageComment/CommentEditor.tsx index 32a00780195..9c8ebe14aac 100644 --- a/apps/app/src/components/PageComment/CommentEditor.tsx +++ b/apps/app/src/components/PageComment/CommentEditor.tsx @@ -13,7 +13,7 @@ import { TabContent, TabPane, } from 'reactstrap'; -import { apiv3Get, apiv3PostForm } from '~/client/util/apiv3-client'; +import { uploadAttachments } from '~/client/services/upload-attachments'; import { toastError } from '~/client/util/toastr'; import type { IEditorMethods } from '~/interfaces/editor-methods'; import { useSWRxPageComment, useSWRxEditingCommentsNum } from '~/stores/comment'; @@ -201,40 +201,21 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => { // the upload event handler const uploadHandler = useCallback((files: File[]) => { - files.forEach(async(file) => { - try { - const { data: resLimit } = await apiv3Get('/attachment/limit', { fileSize: file.size }); - - if (!resLimit.isUploadable) { - throw new Error(resLimit.errorMessage); - } - - const formData = new FormData(); - formData.append('file', file); - if (pageId != null) { - formData.append('page_id', pageId); - } - - const { data: resAdd } = await apiv3PostForm('/attachment', formData); - - const attachment = resAdd.attachment; + uploadAttachments(pageId, files, { + onUploaded: (attachment) => { const fileName = attachment.originalName; - let insertText = `[${fileName}](${attachment.filePathProxied})\n`; - // when image - if (attachment.fileFormat.startsWith('image/')) { - // modify to "![fileName](url)" syntax - insertText = `!${insertText}`; - } + const prefix = attachment.fileFormat.startsWith('image/') + ? '!' // use "![fileName](url)" syntax when image + : ''; + const insertText = `${prefix}[${fileName}](${attachment.filePathProxied})\n`; codeMirrorEditor?.insertText(insertText); - } - catch (e) { - logger.error('failed to upload', e); - toastError(e); - } + }, + onError: (error) => { + toastError(error); + }, }); - }, [codeMirrorEditor, pageId]); const getCommentHtml = useCallback(() => { diff --git a/apps/app/src/components/PageEditor/PageEditor.tsx b/apps/app/src/components/PageEditor/PageEditor.tsx index 0ef4d798066..cba5e374fdd 100644 --- a/apps/app/src/components/PageEditor/PageEditor.tsx +++ b/apps/app/src/components/PageEditor/PageEditor.tsx @@ -21,7 +21,7 @@ import { throttle, debounce } from 'throttle-debounce'; import { useShouldExpandContent } from '~/client/services/layout'; import { useUpdateStateAfterSave } from '~/client/services/page-operation'; import { updatePage, extractRemoteRevisionDataFromErrorObj } from '~/client/services/update-page'; -import { apiv3Get, apiv3PostForm } from '~/client/util/apiv3-client'; +import { uploadAttachments } from '~/client/services/upload-attachments'; import { toastError, toastSuccess, toastWarning } from '~/client/util/toastr'; import { useDefaultIndentSize, useCurrentUser, @@ -237,40 +237,28 @@ export const PageEditor = React.memo((props: Props): JSX.Element => { // the upload event handler const uploadHandler = useCallback((files: File[]) => { - files.forEach(async(file) => { - try { - const { data: resLimit } = await apiv3Get('/attachment/limit', { fileSize: file.size }); - - if (!resLimit.isUploadable) { - throw new Error(resLimit.errorMessage); - } - - const formData = new FormData(); - formData.append('file', file); - if (pageId != null) { - formData.append('page_id', pageId); - } - - const { data: resAdd } = await apiv3PostForm('/attachment', formData); + if (pageId == null) { + logger.error('pageId is invalid', { + pageId, + }); + throw new Error('pageId is invalid'); + } - const attachment = resAdd.attachment; + uploadAttachments(pageId, files, { + onUploaded: (attachment) => { const fileName = attachment.originalName; - let insertText = `[${fileName}](${attachment.filePathProxied})\n`; - // when image - if (attachment.fileFormat.startsWith('image/')) { - // modify to "![fileName](url)" syntax - insertText = `!${insertText}`; - } + const prefix = attachment.fileFormat.startsWith('image/') + ? '!' // use "![fileName](url)" syntax when image + : ''; + const insertText = `${prefix}[${fileName}](${attachment.filePathProxied})\n`; codeMirrorEditor?.insertText(insertText); - } - catch (e) { - logger.error('failed to upload', e); - toastError(e); - } + }, + onError: (error) => { + toastError(error); + }, }); - }, [codeMirrorEditor, pageId]); // set handler to save and return to View diff --git a/apps/app/src/interfaces/apiv3/attachment.ts b/apps/app/src/interfaces/apiv3/attachment.ts new file mode 100644 index 00000000000..53a0c277436 --- /dev/null +++ b/apps/app/src/interfaces/apiv3/attachment.ts @@ -0,0 +1,15 @@ +import type { IAttachment, IPage, IRevision } from '@growi/core'; + +import type { ICheckLimitResult } from '../attachment'; + +export type IApiv3GetAttachmentLimitParams = { + fileSize: number, +}; + +export type IApiv3GetAttachmentLimitResponse = ICheckLimitResult; + +export type IApiv3PostAttachmentResponse = { + page: IPage, + revision: IRevision, + attachment: IAttachment, +}