gridfs/models/File.model.js

/**
 * @module flitter-gridfs/models/File
 */

const Model = require('flitter-orm/src/model/Model')

const path = require('path')
const fs = require('fs')
const Mongo = require('mongodb')

/**
 * Model to represent a stored file.
 * @extends {module:flitter-orm/src/model/Model~Model}
 */
class GridFile extends Model {
    /**
     * Defines the services required by this model.
     * @returns {Array<string>}
     */
    static get services() {
        return [...super.services, 'gridfs']
    }

    /**
     * Defines the schema used by this model.
     * @returns {object}
     */
    static get schema() {
        return {
            name: String,
            content_type: String,
            created_on: {type: Date, default: () => new Date},
            file_id: String,
            data: {type: String, default: '{}'},
        }
    }

    /**
     * Get the GridFS files collection.
     * @returns {Promise<mongodb/Collection>}
     */
    static async files() {
        return this.gridfs.mongo_db.collection('fs.files')
    }

    /**
     * Convert an object ID string to a MongoDB ObjectId.
     * @param {string} id
     * @returns {mongodb/ObjectId}
     */
    static object_id(id) {
        return Mongo.ObjectId(id)
    }

    /**
     * Get a file record by ID string from the grid files collection.
     * @param {string} id
     * @returns {Promise<object>} - resolves to the retrieved file record object
     */
    static async one(id) {
        const files = await this.files()
        return files.findOne({_id: this.object_id(id)})
    }

    /**
     * Get the GridFSBucket used by the model.
     * @returns {mongodb/GridFS/Bucket} - the GridFS bucket
     */
    grid() {
        return this.gridfs.mongo_bucket
    }

    /**
     * Save a file to the grid from a local file.
     * @param {string} filepath - path of the file to be uploaded
     * @param {string} [filename] - name of the file to save, defaults to the model's this.name
     * @returns {Promise<void>}
     */
    write_from_file({filepath, filename}){
        const grid = this.grid()
        filename = filename ? filename : this.name
        filepath = path.resolve(filepath)

        return new Promise((resolve, reject) => {
            fs.createReadStream(filepath)
                .pipe(grid.openUploadStream(filename))
                .on('error', (e) => reject(e))
                .on('finish', (file) => {
                    this.file_id = file._id.toString()
                    this.save().then(resolve)
                })
        })
    }

    /**
     * Retrieve the model's file from the Grid and store it to the local filesystem.
     * @param {string} filepath - path to which the file should be saved
     * @returns {Promise<void>}
     */
    write_to_file({filepath}){
        filepath = path.resolve(filepath)
        return new Promise((resolve, reject) => {
            this.get_read_stream()
                .pipe(fs.createWriteStream(filepath))
                .on('error', e => reject(e))
                .on('finish', () => resolve())
        })
    }

    /**
     * Get the read stream for the model's stored Grid file.
     * @returns {mongodb/GridFS/GridFSBucketReadStream}
     */
    get_read_stream(){
        const grid = this.grid()
        return grid.openDownloadStream(this.constructor.object_id(this.file_id))
    }

    /**
     * Send this model's stored Grid file as data for the specified response.
     * @param {express/Response} response - the response to send to
     * @returns {boolean | void}
     */
    send_to_response(response){
        const stream = this.get_read_stream()
        response.set({
            'Content-Disposition': `inline; filename="${this.name}"`
        })
        return stream.pipe(response)
    }
}

module.exports = exports = GridFile