attachment.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import path from 'path';
  2. import type { IAttachment } from '@growi/core';
  3. import { addSeconds } from 'date-fns/addSeconds';
  4. import {
  5. Schema, type Model, type Document, Types,
  6. } from 'mongoose';
  7. import mongoosePaginate from 'mongoose-paginate-v2';
  8. import uniqueValidator from 'mongoose-unique-validator';
  9. import loggerFactory from '~/utils/logger';
  10. import { AttachmentType } from '../interfaces/attachment';
  11. import { getOrCreateModel } from '../util/mongoose-utils';
  12. // eslint-disable-next-line no-unused-vars
  13. const logger = loggerFactory('growi:models:attachment');
  14. function generateFileHash(fileName) {
  15. const hash = require('crypto').createHash('md5');
  16. hash.update(`${fileName}_${Date.now()}`);
  17. return hash.digest('hex');
  18. }
  19. type GetValidTemporaryUrl = () => string | null | undefined;
  20. type CashTemporaryUrlByProvideSec = (temporaryUrl: string, lifetimeSec: number) => Promise<IAttachmentDocument>;
  21. export interface IAttachmentDocument extends IAttachment, Document {
  22. getValidTemporaryUrl: GetValidTemporaryUrl
  23. cashTemporaryUrlByProvideSec: CashTemporaryUrlByProvideSec,
  24. }
  25. export interface IAttachmentModel extends Model<IAttachmentDocument> {
  26. createWithoutSave: (
  27. pageId, user, originalName: string, fileFormat: string, fileSize: number, attachmentType: AttachmentType,
  28. ) => IAttachmentDocument;
  29. }
  30. const attachmentSchema = new Schema({
  31. page: { type: Schema.Types.ObjectId, ref: 'Page', index: true },
  32. creator: { type: Schema.Types.ObjectId, ref: 'User', index: true },
  33. filePath: { type: String }, // DEPRECATED: remains for backward compatibility for v3.3.x or below
  34. fileName: { type: String, required: true, unique: true },
  35. fileFormat: { type: String, required: true },
  36. fileSize: { type: Number, default: 0 },
  37. originalName: { type: String },
  38. temporaryUrlCached: { type: String },
  39. temporaryUrlExpiredAt: { type: Date },
  40. attachmentType: {
  41. type: String,
  42. enum: AttachmentType,
  43. required: true,
  44. },
  45. }, {
  46. timestamps: { createdAt: true, updatedAt: false },
  47. });
  48. attachmentSchema.plugin(uniqueValidator);
  49. attachmentSchema.plugin(mongoosePaginate);
  50. // virtual
  51. attachmentSchema.virtual('filePathProxied').get(function() {
  52. return `/attachment/${this._id}`;
  53. });
  54. attachmentSchema.virtual('downloadPathProxied').get(function() {
  55. return `/download/${this._id}`;
  56. });
  57. attachmentSchema.set('toObject', { virtuals: true });
  58. attachmentSchema.set('toJSON', { virtuals: true });
  59. attachmentSchema.statics.createWithoutSave = function(
  60. pageId, user, originalName: string, fileFormat: string, fileSize: number, attachmentType: AttachmentType,
  61. ) {
  62. // eslint-disable-next-line @typescript-eslint/no-this-alias
  63. const Attachment = this;
  64. const extname = path.extname(originalName);
  65. let fileName = generateFileHash(originalName);
  66. if (extname.length > 1) { // ignore if empty or '.' only
  67. fileName = `${fileName}${extname}`;
  68. }
  69. const attachment = new Attachment();
  70. attachment.page = pageId;
  71. attachment.creator = user._id;
  72. attachment.originalName = originalName;
  73. attachment.fileName = fileName;
  74. attachment.fileFormat = fileFormat;
  75. attachment.fileSize = fileSize;
  76. attachment.attachmentType = attachmentType;
  77. return attachment;
  78. };
  79. const getValidTemporaryUrl: GetValidTemporaryUrl = function(this: IAttachmentDocument) {
  80. if (this.temporaryUrlExpiredAt == null) {
  81. return null;
  82. }
  83. // return null when expired url
  84. if (this.temporaryUrlExpiredAt.getTime() < new Date().getTime()) {
  85. return null;
  86. }
  87. return this.temporaryUrlCached;
  88. };
  89. attachmentSchema.methods.getValidTemporaryUrl = getValidTemporaryUrl;
  90. const cashTemporaryUrlByProvideSec: CashTemporaryUrlByProvideSec = function(this: IAttachmentDocument, temporaryUrl, lifetimeSec) {
  91. if (temporaryUrl == null) {
  92. throw new Error('url is required.');
  93. }
  94. this.temporaryUrlCached = temporaryUrl;
  95. this.temporaryUrlExpiredAt = addSeconds(new Date(), lifetimeSec);
  96. return this.save();
  97. };
  98. attachmentSchema.methods.cashTemporaryUrlByProvideSec = cashTemporaryUrlByProvideSec;
  99. export const Attachment = getOrCreateModel<IAttachmentDocument, IAttachmentModel>('Attachment', attachmentSchema);