src/custom/cappasity-upload-pre.js
const assert = require('assert');
const Promise = require('bluebird');
const get = require('lodash/get');
const noop = require('lodash/noop');
const includes = require('lodash/includes');
const { HttpStatusError } = require('common-errors');
const isCappasityUpload = require('../utils/is-cappasity-upload');
const {
FILES_PACKED_FIELD,
FILES_USER_INDEX_KEY,
} = require('../constant');
const isPack = (it) => it.type === 'c-pack';
// resolve real username and fetch plan data
function getUserData(alias) {
const { amqp, config } = this;
const { planGet } = config.payments;
const { getMetadata, audience } = config.users;
const promises = [
// get user id
this.hook('files:info:pre', alias),
// fetch current user's plan and roles
amqp.publishAndWait(getMetadata, {
username: alias,
audience: [audience],
fields: {
[audience]: ['roles', 'plan'],
},
}),
];
return Promise.all(promises).spread(([userId], attributes) => {
const { roles, plan } = attributes[audience];
// fetch plan data
return amqp
.publishAndWait(planGet.route, plan, planGet.options)
.then((planData) => ({
userId,
roles,
plan: planData,
}));
});
}
// check limit of uploadings by username
function checkUploadsLimit(params) {
const { redis } = this;
// get user's roles and limit of embeddings according his plan
return Promise
.bind(this, params.username)
.then(getUserData)
.then((data) => {
const { userId, roles, plan } = data;
const isAdmin = includes(roles, 'admin');
// skip next checks if user is admin
if (isAdmin) {
return null;
}
const FILES_PER_USER_SET = FILES_USER_INDEX_KEY(userId);
const embeddings = get(plan, 'meta.embeddings.value', 0);
return redis.scard(FILES_PER_USER_SET).then((uploadedFiles) => {
const isOutOfLimit = uploadedFiles >= embeddings;
if (isOutOfLimit) {
throw new HttpStatusError(412, 'no available embeddings');
}
return null;
});
});
}
module.exports = function extractMetadata(params) {
let sourceSHA;
const {
files,
meta,
temp,
unlisted,
access,
uploadType,
} = params;
// treat all indexed uploads as cappasity models
// https://github.com/makeomatic/ms-files/blob/master/src/actions/finish.js#L112
const cappasityModel = !(unlisted || temp);
return Promise
.try(function verifyUploadData() {
if (uploadType === 'simple' && files.find(isPack)) {
meta[FILES_PACKED_FIELD] = '1';
return null;
}
// use relevant validation
// based on upload type in the json schema
if (uploadType) {
return null;
}
const fileTypes = Object.create(null);
let differentFileTypes = 0;
// calculate file types
files.forEach((props) => {
const { type } = props;
if (fileTypes[type] !== undefined) {
fileTypes[type] += 1;
} else {
differentFileTypes += 1;
fileTypes[type] = 1;
}
if (type === 'c-bin') {
sourceSHA = props['source-sha256'];
}
});
const cappasityUpload = isCappasityUpload(Object.keys(fileTypes));
// assert constraints
if (differentFileTypes === 1) {
if (cappasityUpload) {
assert.equal(fileTypes['c-preview'], 1, 'must contain exactly one preview');
}
if (!unlisted) {
throw new HttpStatusError(412, 'following upload must be unlisted');
}
if (!access.setPublic) {
throw new HttpStatusError(412, 'following upload must be public');
}
} else if (cappasityUpload) {
// must always be true if it's not a simple preview upload
assert.equal(fileTypes['c-bin'], 1, 'must contain exactly one binary upload');
meta.sourceSHA = sourceSHA;
if (meta.export && !temp) {
throw new HttpStatusError(412, 'temp must be set to true');
}
} else {
throw new HttpStatusError(400, 'should be either a cappasity model or a single image');
}
return null;
})
.bind(this)
.return(params)
.then(cappasityModel ? checkUploadsLimit : noop);
};