// Handler functions for the /assets endpoint.

var
    async = require('async'),
    pkgcloud = require('pkgcloud'),
    fs = require('fs'),
    path = require('path'),
    crypto = require('crypto'),
    config = require('./config'),
    connection = require('./connection'),
    log = require('./logging').getLogger();

/**
 * @description Calculate a checksum of an uploaded file's contents to generate
 *   the fingerprinted asset name.
 */
function fingerprintAsset(asset, callback) {
    var sha256sum = crypto.createHash('sha256');
    var assetFile = fs.createReadStream(asset.path);
    var chunks = [];

    assetFile.on('data', function (chunk) {
        sha256sum.update(chunk);
        chunks.push(chunk);
    });

    assetFile.on('error', callback);

    assetFile.on('end', function() {
        var digest = sha256sum.digest('hex');
        var ext = path.extname(asset.name);
        var basename = path.basename(asset.name, ext);
        var fingerprinted = basename + "-" + digest + ext;

        log.debug({
            originalAssetName: asset.name,
            assetFilename: fingerprinted,
            message: "Asset fingerprinted successfully."
        });

        callback(null, {
            key: asset.key,
            original: asset.name,
            chunks: chunks,
            filename: fingerprinted,
            type: asset.type,
        });
    });
}

/**
 * @description Upload an asset's contents to the asset container.
 */
function publishAsset(asset, callback) {
    var up = connection.client.upload({
        container: config.assetContainer(),
        remote: asset.filename,
        contentType: asset.type,
        headers: { 'Access-Control-Allow-Origin': '*' }
    });

    up.on('error', callback);

    up.on('success', function () {
        log.debug({
            assetFilename: asset.filename,
            message: "Asset uploaded successfully."
        });

        var baseURI = connection.assetContainer.cdnSslUri;
        asset.publicURL = baseURI + '/' + encodeURIComponent(asset.filename);
        callback(null, asset);
    });

    asset.chunks.forEach(function (chunk) {
        up.write(chunk);
    });

    up.end();
}

/**
 * @description Give this asset a name. The final name and CDL URI of this
 *   asset will be included in all outgoing metadata envelopes, for use by
 *   layouts.
 */
function nameAsset(asset, callback) {
    log.debug({
        originalAssetFilename: asset.original,
        assetName: asset.key,
        message: "Asset named successfully."
    });

    connection.db.collection("layoutAssets").updateOne(
        { key: asset.key },
        { $set: { key: asset.key, publicURL: asset.publicURL } },
        { upsert: true },
        function (err) { callback(err, asset); }
    );
}

/**
 * @description Create and return a function that processes a single asset.
 */
function makeAssetHandler(should_name) {
    return function(asset, callback) {
        log.debug({
            originalAssetName: asset.name,
            message: "Asset upload request received."
        });

        var steps = [
            async.apply(fingerprintAsset, asset),
            publishAsset
        ];

        if (should_name) {
            steps.push(nameAsset);
        }

        async.waterfall(steps, callback);
    };
}

/**
 * @description Enumerate all named assets.
 */
var enumerateNamed = exports.enumerateNamed = function (callback) {
    connection.db.collection("layoutAssets").find().toArray(function (err, assetVars) {
        if (err) {
            return callback(err);
        }

        var assets = {};

        for (i = 0; i < assetVars.length; i++) {
            var assetVar = assetVars[i];
            assets[assetVar.key] = assetVar.publicURL;
        }

        callback(null, assets);
    });
};

/**
 * @description Fingerprint and upload static, binary assets to the
 *   CDN-enabled ASSET_CONTAINER. Return a JSON object containing a
 *   map of the provided filenames to their final, public URLs.
 */
exports.accept = function (req, res, next) {
    var assetData = Object.getOwnPropertyNames(req.files).map(function (key) {
        var asset = req.files[key];
        asset.key = key;
        return asset;
    });

    log.debug({
        apikeyName: req.apikeyName,
        assetCount: assetData.length,
        message: "Asset upload request received."
    });

    var reqStart = Date.now();

    async.map(assetData, makeAssetHandler(req.query.named), function (err, results) {
        if (err) {
            log.error({
                apikeyName: req.apikeyName,
                error: err.message,
                message: "Unable to upload one or more assets."
            });

            res.send(500, {
                apikeyName: req.apikeyName,
                error: "Unable to upload one or more assets."
            });
            return next();
        }

        var summary = {};
        results.forEach(function (result) {
            summary[result.original] = result.publicURL;
        });
        log.info({
            apikeyName: req.apikeyName,
            totalReqDuration: Date.now() - reqStart,
            message: "All assets have been uploaded successfully.",
            summary: summary
        });

        res.send(summary);
        next();
    });
};

exports.list = function (req, res, next) {
    log.debug("Asset list requested.");

    enumerateNamed(function (err, assets) {
        if (err) {
            log.error({
                message: "Unable to list assets.",
                error: err.message
            });
            return next(err);
        }

        res.send(assets);
        next();
    });
};

@@ -1014,32 +1014,66 @@
     log.debug(%7B%0A
+            action: 'assetstore',%0A
             orig
@@ -1824,32 +1824,66 @@
     log.debug(%7B%0A
+            action: 'assetstore',%0A
             asse
@@ -2464,32 +2464,62 @@
     log.debug(%7B%0A
+        action: 'assetstore',%0A
         original
@@ -3044,32 +3044,66 @@
     log.debug(%7B%0A
+            action: 'assetstore',%0A
             orig
@@ -4360,32 +4360,62 @@
     log.debug(%7B%0A
+        action: 'assetstore',%0A
         apikeyNa
@@ -4676,35 +4676,166 @@
             
-log.error(%7B
+var statusCode = err.statusCode %7C%7C 500;%0A%0A            log.error(%7B%0A                action: 'assetstore',%0A                statusCode: statusCode,
 %0A           
@@ -5005,19 +5005,26 @@
 es.send(
-500
+statusCode
 , %7B%0A    
@@ -5133,32 +5133,33 @@
             %7D);%0A
+%0A
             retu
@@ -5162,24 +5162,27 @@
 return next(
+err
 );%0A        %7D
@@ -5341,16 +5341,79 @@
 .info(%7B%0A
+            action: 'assetstore',%0A            statusCode: 200,%0A
         
@@ -5825,32 +5825,120 @@
     log.error(%7B%0A
+                action: 'assetlist',%0A                statusCode: err.statusCode %7C%7C 500,%0A
                 

Tag asset messages with actions.