Skip to content

Commit

Permalink
Merge pull request #8689 from weseek/support/commonize-upload-attachm…
Browse files Browse the repository at this point in the history
…ents-operation

support: Commonize upload-attachments operation
  • Loading branch information
yuki-takei committed Apr 5, 2024
2 parents 9f322f0 + 6cd82b6 commit 8d0bd84
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 66 deletions.
1 change: 1 addition & 0 deletions apps/app/src/client/services/upload-attachments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './upload-attachments';
Original file line number Diff line number Diff line change
@@ -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<void> => {
files.forEach(async(file) => {
try {
const params: IApiv3GetAttachmentLimitParams = { fileSize: file.size };
const { data: resLimit } = await apiv3Get<IApiv3GetAttachmentLimitResponse>('/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<IApiv3PostAttachmentResponse>('/attachment', formData);

opts?.onUploaded?.(resAdd.attachment);
}
catch (e) {
logger.error('failed to upload', e);
opts?.onError?.(e, file);
}
});
};
41 changes: 11 additions & 30 deletions apps/app/src/components/PageComment/CommentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(() => {
Expand Down
46 changes: 17 additions & 29 deletions apps/app/src/components/PageEditor/PageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions apps/app/src/interfaces/apiv3/attachment.ts
Original file line number Diff line number Diff line change
@@ -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,
}
5 changes: 5 additions & 0 deletions apps/app/src/interfaces/attachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ import type { PaginateResult } from './mongoose-utils';
export type IResAttachmentList = {
paginateResult: PaginateResult<IAttachmentHasId>
};

export type ICheckLimitResult = {
isUploadable: boolean,
errorMessage?: string,
}
10 changes: 3 additions & 7 deletions apps/app/src/server/service/file-uploader/file-uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,11 +18,6 @@ export type SaveFileParam = {
data,
}

export type CheckLimitResult = {
isUploadable: boolean,
errorMessage?: string,
}

export type TemporaryUrl = {
url: string,
lifetimeSec: number,
Expand All @@ -38,7 +34,7 @@ export interface FileUploader {
deleteFiles(): void,
getFileUploadTotalLimit(): number,
getTotalFileSize(): Promise<number>,
doCheckLimit(uploadFileSize: number, maxFileSize: number, totalLimit: number): Promise<CheckLimitResult>,
doCheckLimit(uploadFileSize: number, maxFileSize: number, totalLimit: number): Promise<ICheckLimitResult>,
determineResponseMode(): ResponseMode,
respond(res: Response, attachment: IAttachmentDocument, opts?: RespondOptions): void,
findDeliveryFile(attachment: IAttachmentDocument): Promise<NodeJS.ReadableStream>,
Expand Down Expand Up @@ -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<CheckLimitResult> {
async doCheckLimit(uploadFileSize: number, maxFileSize: number, totalLimit: number): Promise<ICheckLimitResult> {
if (uploadFileSize > maxFileSize) {
return { isUploadable: false, errorMessage: 'File size exceeds the size limit per file' };
}
Expand Down

0 comments on commit 8d0bd84

Please sign in to comment.