UNPKG

@scefira/dfw-nodejs

Version:
457 lines (388 loc) 15.3 kB
import { DFWScheme } from "../.."; import DFWModule from "./DFWModule"; import DFWUtils from "../DFWUtils"; import moment from "moment"; import dfw_file from "../../model/dfw_file.model"; import fileUpload from "express-fileupload"; import { Op } from "sequelize"; import { promisify, isArray, isNumber } from "util"; import md5File from 'md5-file/promise'; import * as Express from "express"; import * as fs from "fs"; import * as os from "os"; import * as nodejsPath from "path"; import * as UUID from "uuid"; const fileExistsAsync = promisify(fs.exists); const fileRenameAsync = promisify(fs.rename); const fileMakeDir = promisify(fs.mkdir); const fileMakeTempDir = promisify(fs.mkdtemp); const fileStat = promisify(fs.stat); const fileUnlink = promisify(fs.unlink); const fileCopy = promisify(fs.copyFile); export type UploadOptions = { path?:string; // Destination path relative to root of the project name?:string; // filename slug?:string; // sllug for file ext?:string; // file extensión set manually expiration?:Date|moment.Moment|null;// Expiration of a the file in the server (null is never expire) access?:number; // acces type defined by the access constants in dfw_file model description?:string; // description text for help or alt attributes parent?:dfw_file|FileRecord|number; // file parent for file trees variant?:string; // file variant to diferenciate from another childs copy?:boolean; // configuration indicates tha file origin will be copied inted of cuted } export type UploadConfig = { headers?: any; highWaterMark?: number; fileHwm?: number; defCharset?: string; preservePath?: boolean; limits?: { fieldNameSize?: number; fieldSize?: number; fields?: number; fileSize?: number; files?: number; parts?: number; headerPairs?: number; }; } export type FileRecord = { id:number; description:string; url:string; size:number; expire:Date; created:Date; children?:FileRecord[]; variant:string; } export type FileRecordOptions = { recursive?:boolean; } export interface UploadedFile { name: string; encoding: string; mimetype: string; data: Buffer; size: number; tempFilePath: string; truncated: boolean; md5: string; mv(path: string, callback: (err: any) => void): void; mv(path: string): Promise<void>; } export default class UploadManager extends DFWModule{ static readonly ACCESS_PUBLIC = 0; // Everybody can access static readonly ACCESS_SESSION = 1; // Onli loged user can access static readonly ACCESS_PRIVATE = 2; // Onli the owner user and file_access member can access private readonly TMPDIR:string; public constructor(dfw){ super(dfw); if(this.DFWInstance.config.upload && this.DFWInstance.config.upload.tempDir){ this.TMPDIR = nodejsPath.join(this.DFWInstance.config.upload.tempDir, UUID.v4()); }else{ this.TMPDIR = nodejsPath.join(os.tmpdir(),UUID.v4()); } if(fs.existsSync(this.TMPDIR) == false){ fs.mkdirSync(this.TMPDIR,{recursive:true}) } fileMakeTempDir(this.TMPDIR).catch(); dfw.server.use(this.getStaticUploadPath(),Express.static(this.getLocalUploadDir())) // Public static upload path setInterval(()=>{ // Clear expired files each 6 hours this.purge(); },1000*60*60*6); } /** * @param dfw */ public async touchAsync(dfw:DFWScheme){ dfw.file = { validateFileAsync: async (file:number|dfw_file|FileRecord,expire:Date|null = null)=>{ return await this.validateFileAsync(file,expire); }, invalidateFileAsync: async (file:number|dfw_file|FileRecord)=>{ return await this.invalidateFileAsync(file); }, flushUploadAsync: async (file:UploadedFile,config:UploadOptions = {})=>{ return await this.flushUploadedFileAsync(dfw,file,config); }, getFileRecordAsync: async (file:number|number[]|dfw_file|dfw_file[],options?:FileRecordOptions)=>{ return await this.getFileRecordDataAsync(file,options); }, getFileRecord: (file:dfw_file|dfw_file[])=>{ return this.getFileRecordData(file); }, checkFileAccessAsync: async (file:number|dfw_file)=>{ return await this.checkFileAccessAsync(dfw,file); }, removeFileAsync: async (file:number|dfw_file|FileRecord)=>{ return await this.removeFileAsync(file); }, } } /** * Flushes the uploaded file to save in DFW database * @param dfw * @param currentPath * @param config */ public async flushUploadedFileAsync(dfw:DFWScheme,file:UploadedFile,config:UploadOptions):Promise<dfw_file>{ let currentPath = file.tempFilePath; if(file === undefined || file === null){ throw new Error("File cannot be undefined"); } if(file.truncated === true){ fileUnlink(file.tempFilePath); throw new Error(`maximum file size exceeded (${(file.size/1024).toFixed(0)} Kb)`); } return this.assingLocalFileAsync(dfw,currentPath,{...config, name:file.name }); } /** * * @param file */ public async invalidateFileAsync(file:number|dfw_file|FileRecord):Promise<dfw_file>{ let fileId:number = isNumber(file)?file:file.id; let fileObj:dfw_file; if(file instanceof dfw_file){ fileObj = file; }else{ fileObj = await dfw_file.findByPk(fileId) } if(!fileObj){ throw `Error file with id ${fileId} not exists`; } fileObj.expire = moment().toDate(); return await fileObj.save(); } /** * * @param file * @param expire */ public async validateFileAsync(file:number|dfw_file|FileRecord,expire:Date|null = null):Promise<dfw_file>{ let fileId:number = isNumber(file)?file:file.id; let fileObj:dfw_file; if(file instanceof dfw_file){ fileObj = file; }else{ fileObj = await dfw_file.findByPk(fileId) } if(!fileObj){ throw `Error file with id ${fileId} not exists`; } if(expire === null || expire instanceof Date){ fileObj.expire = expire; }else if(isNumber(expire)){ fileObj.expire = moment().add("days",expire).toDate(); } return await fileObj.save(); } /** * * @param dfw * @param filePath * @param options */ public async assingLocalFileAsync(dfw:DFWScheme,filePath:string,options:UploadOptions = {}){ if(await fileExistsAsync(filePath) == false){ throw new Error(`Process uploaded file async error, unable to find file ${filePath}`); } let originalBaseName = nodejsPath.basename(options.name?options.name:filePath); let md5 = await md5File(filePath); let stats = await fileStat(filePath); let mimetype = DFWUtils.getMimetype(options.ext?options.ext:originalBaseName); let ext = options.ext?options.ext:DFWUtils.getExtension(originalBaseName); let filename = `${DFWUtils.uuid()}.${ext}`; let partialPath = moment().format("YYYY/MM"); let path = options.path?options.path:`${this.getLocalUploadDir()}`; let expire = options.expiration?options.expiration:options.expiration === undefined?moment().add(1,"day"):null; let finalFilePath = `${path}/${partialPath}/${filename}`; let description = options.description?options.description:DFWUtils.getBaseName(originalBaseName); let variant = options.variant?options.variant:""; let idFileParent = options.parent?isNumber(options.parent)?options.parent:options.parent.id:null; if(await fileExistsAsync(`${path}/${partialPath}`) == false){ await fileMakeDir(`${path}/${partialPath}`,{recursive:true}); } if(options.copy){ await fileCopy(filePath,finalFilePath).catch((e)=>{ throw new Error("Process uploaded file async error, unable to copy file uploaded: " + e); }) }else{ await fileRenameAsync(filePath,finalFilePath).catch((e)=>{ throw new Error("Process uploaded file async error, unable to move file uploaded: " + e); }) } return dfw_file.create({ slug:options.slug, size:stats.size, idUser: dfw.session.model.idUser, name:filename, access:UploadManager.ACCESS_PUBLIC, checksum:md5, description, expire, mimetype, path, variant, partialPath, idFileParent }); } /** * * @param dfw * @param path * @param parent * @param variant * @param options */ public async assignChildLocalFileAsync(dfw:DFWScheme,path:string,parent:number|dfw_file|FileRecord,variant?:string,options:UploadOptions = {}){ await this.assingLocalFileAsync(dfw,path,Object.assign(options,{ parent : isNumber(parent)?parent:parent.id , variant })); } /** * * @param file */ public getFileRecordData(file:dfw_file|dfw_file[],options:FileRecordOptions = {}):FileRecord|FileRecord[]{ if(isArray(file)){ return file.map((f)=>this.getFileRecordData(f,options) as FileRecord); }else{ return { id:file.id, url:file.url, size:file.size, description:file.description, variant:file.variant, expire:file.expire, created:file.created_at, children: options.recursive?file.children?file.children.map((cf)=>this.getFileRecordData(cf,options)):[]:[], } as FileRecord; } } /** * Static method * @param DFW * @param file */ public async getFileRecordDataAsync(file:number|dfw_file|number[]|dfw_file[],options:FileRecordOptions = {}):Promise<FileRecord|FileRecord[]|undefined>{ if(!file) return; if(isArray(file)){ let res = [] as FileRecord[]; for(let f of file){ res.push((await this.getFileRecordDataAsync(f,options)) as any); } return res; } if(file instanceof dfw_file){ return this.getFileRecordData(file,options); }else{ return await this.getFileRecordDataAsync(await this.DFWInstance.getDatabase().retriveModel(dfw_file).findByPk(file,{include:[{ association:"children" }]}),options); } } /** * */ public getLocalPrivateUploadDir(){ if(this.DFWInstance.config.upload !== undefined && this.DFWInstance.config.upload.localUploadDir !== undefined) return this.DFWInstance.config.upload.localUploadDir; return "upload/private"; } /** * */ public getLocalUploadDir(){ if(this.DFWInstance.config.upload !== undefined && this.DFWInstance.config.upload.localUploadDir !== undefined) return this.DFWInstance.config.upload.localUploadDir; return "upload/public"; } /** * */ public getStaticUploadPath(){ if(this.DFWInstance.config.upload !== undefined && this.DFWInstance.config.upload.staticUploadPath !== undefined) return this.DFWInstance.config.upload.staticUploadPath; return "/upload"; } /** * */ public getStaticUploadSlugPath(){ return nodejsPath.join(this.getStaticUploadPath(),"/s"); } /** * Generate pre-configured middleware for file uploading * @param config */ public makeUploadMiddleware(config?:UploadConfig){ return fileUpload(Object.assign({ limits: { fileSize: (5 * 1024 * 1024) } }, config, { useTempFiles : true , tempFileDir : this.TMPDIR })); } /** * * @param file */ public async removeFileAsync(file:number|dfw_file|FileRecord){ if(file instanceof dfw_file){ let path = file.localPath; await fileUnlink(path).then(()=>{ file.destroy(); }).catch(()=>{ throw `Unable to remvoe file`}) }else{ let fo = await dfw_file.findByPk(isNumber(file)?file:file.id); if(fo){ return this.removeFileAsync(fo); } } } /** * TODO * @param dfw * @param file */ public async checkFileAccessAsync(dfw:DFWScheme,file:number|dfw_file|FileRecord):Promise<boolean>{ return false; } public getFileURL(file:dfw_file){ return `${this.getStaticUploadPath()}/${file.partialPath}/${file.name}`; } public getFileLocalPath(file:dfw_file){ return `${file.path}/${file.partialPath}/${file.name}`; } /** * Delete all expired uploaded files in the local file system */ public async purge(){ let data:dfw_file[] = await this.DFWInstance.getDatabase().retriveModel(dfw_file).findAll({ where:{ expire:{ [Op.lt] :moment() }}}); data.forEach((file)=>{ fileUnlink(file.path).then(()=>{ // remove file file.destroy(); // remove record db }); }); } } export interface DFWFileSystemSchema{ /** * Flushes the upload with the file and uploadConfig */ flushUploadAsync: (file:UploadedFile,config?:UploadOptions)=>Promise<dfw_file|null> /*** * */ getFileRecordAsync:(file:number|dfw_file|number[]|dfw_file[],options?:FileRecordOptions)=>Promise<FileRecord|FileRecord[]|undefined>; /*** * */ getFileRecord:(file:dfw_file|dfw_file[])=>FileRecord|FileRecord[]|null; /** * */ checkFileAccessAsync:(file:number|dfw_file)=>Promise<boolean> /** * */ validateFileAsync:(file:number|dfw_file|FileRecord,expire?:Date|null)=>Promise<dfw_file>; /** * */ invalidateFileAsync:(file:number|dfw_file|FileRecord)=>Promise<dfw_file>; /** * */ removeFileAsync:(file:number|dfw_file|FileRecord)=>Promise<void>; }