| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- import type { IAttachment, Ref } from '@growi/core/dist/interfaces';
- import type { ReadStream } from 'fs';
- import type { HydratedDocument } from 'mongoose';
- import loggerFactory from '~/utils/logger';
- import type Crowi from '../crowi';
- import { AttachmentType } from '../interfaces/attachment';
- import type { IAttachmentDocument } from '../models/attachment';
- import { Attachment } from '../models/attachment';
- const fs = require('fs');
- const mongoose = require('mongoose');
- const logger = loggerFactory('growi:service:AttachmentService');
- const createReadStream = (filePath: string): ReadStream => {
- return fs.createReadStream(filePath, {
- flags: 'r',
- encoding: null,
- fd: null,
- mode: '0666',
- autoClose: true,
- });
- };
- type AttachHandler = (
- pageId: string | null,
- attachment: IAttachmentDocument,
- file: Express.Multer.File,
- ) => Promise<void>;
- type DetachHandler = (attachmentId: string) => Promise<void>;
- type IAttachmentService = {
- createAttachment(
- file: Express.Multer.File,
- user: any,
- pageId: string | null,
- attachmentType: AttachmentType,
- disposeTmpFileCallback?: (file: Express.Multer.File) => void,
- ): Promise<IAttachmentDocument>;
- removeAllAttachments(attachments: IAttachmentDocument[]): Promise<void>;
- removeAttachment(attachmentId: Ref<IAttachment> | undefined): Promise<void>;
- isBrandLogoExist(): Promise<boolean>;
- addAttachHandler(handler: AttachHandler): void;
- addDetachHandler(handler: DetachHandler): void;
- };
- /**
- * the service class for Attachment and file-uploader
- */
- export class AttachmentService implements IAttachmentService {
- attachHandlers: AttachHandler[] = [];
- detachHandlers: DetachHandler[] = [];
- crowi: Crowi;
- constructor(crowi: Crowi) {
- this.crowi = crowi;
- }
- async createAttachment(
- file,
- user,
- pageId: string | null | undefined = null,
- attachmentType,
- disposeTmpFileCallback,
- ): Promise<IAttachmentDocument> {
- const { fileUploadService } = this.crowi;
- // check limit
- const res = await fileUploadService.checkLimit(file.size);
- if (!res.isUploadable) {
- throw new Error(res.errorMessage);
- }
- // create an Attachment document and upload file
- let attachment: IAttachmentDocument;
- let readStreamForCreateAttachmentDocument: ReadStream | null = null;
- try {
- readStreamForCreateAttachmentDocument = createReadStream(file.path);
- attachment = Attachment.createWithoutSave(
- pageId,
- user,
- file.originalname,
- file.mimetype,
- file.size,
- attachmentType,
- );
- await fileUploadService.uploadAttachment(
- readStreamForCreateAttachmentDocument,
- attachment,
- );
- await attachment.save();
- const attachHandlerPromises = this.attachHandlers.map((handler) => {
- return handler(pageId, attachment, file);
- });
- // Do not await, run in background
- Promise.all(attachHandlerPromises)
- .catch((err) => {
- logger.error('Error while executing attach handler', err);
- })
- .finally(() => {
- disposeTmpFileCallback?.(file);
- });
- } catch (err) {
- logger.error('Error while creating attachment', err);
- disposeTmpFileCallback?.(file);
- throw err;
- } finally {
- readStreamForCreateAttachmentDocument?.destroy();
- }
- return attachment;
- }
- async removeAllAttachments(
- attachments: HydratedDocument<IAttachmentDocument>[],
- ): Promise<void> {
- const { fileUploadService } = this.crowi;
- const attachmentsCollection = mongoose.connection.collection('attachments');
- const unorderAttachmentsBulkOp =
- attachmentsCollection.initializeUnorderedBulkOp();
- if (attachments.length === 0) {
- return;
- }
- attachments.forEach((attachment) => {
- unorderAttachmentsBulkOp.find({ _id: attachment._id }).delete();
- });
- await unorderAttachmentsBulkOp.execute();
- fileUploadService.deleteFiles(attachments);
- return;
- }
- async removeAttachment(
- attachmentId: Ref<IAttachment> | undefined,
- ): Promise<void> {
- const { fileUploadService } = this.crowi;
- const attachment = await Attachment.findById(attachmentId);
- if (attachment == null) {
- throw new Error(`Attachment not found: ${attachmentId}`);
- }
- await fileUploadService.deleteFile(attachment);
- await attachment.remove();
- const detachedHandlerPromises = this.detachHandlers.map((handler) => {
- return handler(attachment._id);
- });
- // Do not await, run in background
- Promise.all(detachedHandlerPromises).catch((err) => {
- logger.error('Error while executing detached handler', err);
- });
- return;
- }
- async isBrandLogoExist(): Promise<boolean> {
- const query = { attachmentType: AttachmentType.BRAND_LOGO };
- const count = await Attachment.countDocuments(query);
- return count >= 1;
- }
- /**
- * Register a handler that will be called after attachment creation
- * @param {(pageId: string, attachment: Attachment, file: Express.Multer.File) => Promise<void>} handler
- */
- addAttachHandler(handler: AttachHandler): void {
- this.attachHandlers.push(handler);
- }
- /**
- * Register a handler that will be called before attachment deletion
- * @param {(attachmentId: string) => Promise<void>} handler
- */
- addDetachHandler(handler: DetachHandler): void {
- this.detachHandlers.push(handler);
- }
- }
|