upload/UploadUnit.js

/**
 * @module flitter-upload/UploadUnit
 */

const Unit = require('libflitter/Unit')
const File = require('./model/File')
const FlitterStore = require('./store/FlitterStore')
const AWSS3Store = require('./store/AWSS3Store')
const ncp = require('ncp')
const path = require('path')

/**
 * Unit that prepares and provides services for uploading,
 * accessing, and sending files.
 * @extends module:libflitter/Unit~Unit
 */
class UploadUnit extends Unit {
    /**
     * Defines the services required by this unit.
     * @returns {Array<string>}
     */
    static get services() {
        return [...super.services, 'models', 'configs', 'output', 'canon', 'utility']
    }

    /**
     * Get the name of the service provided by this unit: 'upload'
     * @returns {string} - 'upload'
     */
    static get name() {
        return 'upload'
    }

    /**
     * Object mapping store type names to static class references
     * for the store types supported by flitter-upload.
     *
     * Should map string -> {@link module:flitter-upload/store/Store~Store} class definitions.
     *
     * Supported types:
     * - FlitterStore
     *
     * @type {object}
     */
    store_classes = { FlitterStore, AWSS3Store }

    /**
     * Store configurations loaded from 'configs::upload.stores'.
     * @type {object}
     */
    store_configs = {}

    /**
     * Name of the default store. False if not set. Loaded
     * from 'configs::upload.default_store'.
     * @type {boolean|string}
     */
    default_store_name = false

    /**
     * Mapping of store names to instances of the stores themselves.
     * e.g. maps string -> {@link module:flitter-upload/store/Store~Store}.
     * @type {object}
     */
    stores = {}

    /**
     * Initialize the unit. Load the configurations from config files,
     * then instantiate the configured stores.
     * @param {module:libflitter/app/FlitterApp~FlitterApp} app - the application
     * @returns {Promise<void>}
     */
    async go(app) {
        const di = app.di()
        const upload_config = this.configs.get('upload')
        this.store_configs = upload_config ? upload_config.stores : undefined
        this.default_store_name = upload_config ? upload_config.default_store : undefined

        this.models.external_model(this, 'File', File)

        for ( const provider of Object.values(this.store_classes) ) {
            di.make(provider)
        }

        if ( this.store_configs ) {
            for (const store_name in this.store_configs) {
                if (!this.store_configs.hasOwnProperty(store_name)) continue
                const config = this.store_configs[store_name]
                config.name = store_name
                if (!config.type || !(Object.keys(this.store_classes).includes(config.type))) {
                    this.output.warn(`Invalid or missing upload store type for ${store_name}.`)
                    continue
                }

                const StoreClass = this.store_classes[config.type]
                const store = new StoreClass(config)
                await store.init()
                this.stores[store_name] = store
            }
        }

        this.canon.register_resource('uploader', (name) => this.get(name))
    }

    /**
     * Handler for the 'upload' deployment. This creates the 'upload:UploadFile'
     * middleware, and the upload configuration.
     * @returns {Promise<void>}
     */
    async deploy() {
        const package_dir = __dirname
        const base_dir = path.dirname(this.utility.root())

        this.output.info(`Deploying uploader resources from ${package_dir} to ${base_dir}.`, 0)

        function do_copy(from, to){
            return new Promise(
                (resolve, reject) => {
                    ncp(from, to, (error) => {
                        if ( error ) reject(error)

                        resolve()
                    })
                }
            )
        }
        await do_copy(path.resolve(package_dir, 'deploy'), base_dir)
    }

    /**
     * Get the default file store provider.
     * @returns {module:flitter-upload/store/Store~Store}
     */
    provider() {
        return this.get(this.default_store_name)
    }

    /**
     * Get a configured store by name.
     * This is registered as the 'uploader' canonical resolver.
     * @param {string} store_name
     * @returns {module:flitter-upload/store/Store~Store}
     */
    get(store_name) {
        return this.stores[store_name]
    }
}

module.exports = exports = UploadUnit