import path from "path"; import { BeforeDestroy, BelongsTo, Column, Default, ForeignKey, IsIn, Table, DataType, IsNumeric, } from "sequelize-typescript"; import { publicS3Endpoint, deleteFromS3, getFileByKey, getSignedUrl, } from "@server/utils/s3"; import Document from "./Document"; import Team from "./Team"; import User from "./User"; import IdModel from "./base/IdModel"; import Fix from "./decorators/Fix"; import Length from "./validators/Length"; @Table({ tableName: "attachments", modelName: "attachment" }) @Fix class Attachment extends IdModel { @Length({ max: 4096, msg: "key must be 4096 characters or less", }) @Column key: string; @Length({ max: 255, msg: "contentType must be 255 characters or less", }) @Column contentType: string; @IsNumeric @Column(DataType.BIGINT) size: number; @Default("public-read") @IsIn([["private", "public-read"]]) @Column acl: string; @Column lastAccessedAt: Date | null; @Column expiresAt: Date | null; // getters /** * Get the original uploaded file name. */ get name() { return path.parse(this.key).base; } /** * Whether the attachment is private or not. */ get isPrivate() { return this.acl === "private"; } /** * Get the contents of this attachment as a Buffer */ get buffer() { return getFileByKey(this.key); } /** * Get a url that can be used to download the attachment if the user has a valid session. */ get url() { return this.isPrivate ? this.redirectUrl : this.canonicalUrl; } /** * Get a url that can be used to download a private attachment if the user has a valid session. */ get redirectUrl() { return `/api/attachments.redirect?id=${this.id}`; } /** * Get a direct URL to the attachment in storage. Note that this will not work for private attachments, * a signed URL must be used. */ get canonicalUrl() { return encodeURI(`${publicS3Endpoint()}/${this.key}`); } /** * Get a signed URL with the default expirt to download the attachment from storage. */ get signedUrl() { return getSignedUrl(this.key); } // hooks @BeforeDestroy static async deleteAttachmentFromS3(model: Attachment) { await deleteFromS3(model.key); } // associations @BelongsTo(() => Team, "teamId") team: Team; @ForeignKey(() => Team) @Column(DataType.UUID) teamId: string; @BelongsTo(() => Document, "documentId") document: Document; @ForeignKey(() => Document) @Column(DataType.UUID) documentId: string | null; @BelongsTo(() => User, "userId") user: User; @ForeignKey(() => User) @Column(DataType.UUID) userId: string; } export default Attachment;