2016-11-02 19:08:53 +01:00
|
|
|
import archiver from 'archiver';
|
2019-03-02 10:35:40 +01:00
|
|
|
import format from 'date-fns/format';
|
2017-07-19 19:56:52 +02:00
|
|
|
import isUrl from 'is-url';
|
|
|
|
import jsdom, { serializeDocument } from 'jsdom';
|
2019-03-02 10:35:40 +01:00
|
|
|
import isBefore from 'date-fns/is_before';
|
2019-04-17 20:08:33 +02:00
|
|
|
import isAfter from 'date-fns/is_after';
|
2019-03-02 10:35:40 +01:00
|
|
|
import request from 'request';
|
|
|
|
import slugify from 'slugify';
|
2017-02-22 20:29:35 +01:00
|
|
|
import Project from '../models/project';
|
|
|
|
import User from '../models/user';
|
2019-03-02 10:35:40 +01:00
|
|
|
import { resolvePathToFile } from '../utils/filePath';
|
|
|
|
import generateFileSystemSafeName from '../utils/generateFileSystemSafeName';
|
2017-04-13 20:17:30 +02:00
|
|
|
import { deleteObjectsFromS3, getObjectKey } from './aws.controller';
|
2016-06-17 20:11:52 +02:00
|
|
|
|
|
|
|
export function createProject(req, res) {
|
2016-07-15 19:36:33 +02:00
|
|
|
let projectValues = {
|
2017-01-13 23:32:06 +01:00
|
|
|
user: req.user._id
|
2016-06-24 00:29:55 +02:00
|
|
|
};
|
2016-06-20 22:29:08 +02:00
|
|
|
|
2016-07-15 19:36:33 +02:00
|
|
|
projectValues = Object.assign(projectValues, req.body);
|
2016-06-24 00:29:55 +02:00
|
|
|
|
|
|
|
Project.create(projectValues, (err, newProject) => {
|
2017-02-22 20:29:35 +01:00
|
|
|
if (err) {
|
|
|
|
res.json({ success: false });
|
|
|
|
return;
|
|
|
|
}
|
2018-05-05 02:22:39 +02:00
|
|
|
Project.populate(
|
|
|
|
newProject,
|
2016-11-17 17:15:35 +01:00
|
|
|
{ path: 'user', select: 'username' },
|
2016-07-15 17:54:47 +02:00
|
|
|
(innerErr, newProjectWithUser) => {
|
2017-02-22 20:29:35 +01:00
|
|
|
if (innerErr) {
|
|
|
|
res.json({ success: false });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
res.json(newProjectWithUser);
|
2018-05-05 02:22:39 +02:00
|
|
|
}
|
|
|
|
);
|
2016-06-24 00:29:55 +02:00
|
|
|
});
|
2016-06-19 00:33:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function updateProject(req, res) {
|
2017-02-22 20:29:35 +01:00
|
|
|
Project.findById(req.params.project_id, (findProjectErr, project) => {
|
2018-01-09 21:57:49 +01:00
|
|
|
if (!project.user.equals(req.user._id)) {
|
2017-02-22 20:29:35 +01:00
|
|
|
res.status(403).send({ success: false, message: 'Session does not match owner of project.' });
|
|
|
|
return;
|
2017-01-24 21:29:25 +01:00
|
|
|
}
|
2019-04-17 20:08:33 +02:00
|
|
|
if (req.body.updatedAt && isAfter(new Date(project.updatedAt), req.body.updatedAt)) {
|
|
|
|
res.status(409).send({ success: false, message: 'Attempted to save stale version of project.' });
|
|
|
|
return;
|
|
|
|
}
|
2018-05-05 02:22:39 +02:00
|
|
|
Project.findByIdAndUpdate(
|
|
|
|
req.params.project_id,
|
2017-01-13 23:17:31 +01:00
|
|
|
{
|
|
|
|
$set: req.body
|
2017-03-02 20:38:29 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
new: true
|
2018-05-05 02:22:39 +02:00
|
|
|
}
|
|
|
|
)
|
2017-01-13 23:17:31 +01:00
|
|
|
.populate('user', 'username')
|
2017-02-22 20:29:35 +01:00
|
|
|
.exec((updateProjectErr, updatedProject) => {
|
|
|
|
if (updateProjectErr) {
|
|
|
|
console.log(updateProjectErr);
|
|
|
|
res.json({ success: false });
|
|
|
|
return;
|
2017-01-13 23:17:31 +01:00
|
|
|
}
|
|
|
|
if (updatedProject.files.length !== req.body.files.length) {
|
|
|
|
const oldFileIds = updatedProject.files.map(file => file.id);
|
|
|
|
const newFileIds = req.body.files.map(file => file.id);
|
|
|
|
const staleIds = oldFileIds.filter(id => newFileIds.indexOf(id) === -1);
|
2017-02-22 20:29:35 +01:00
|
|
|
staleIds.forEach((staleId) => {
|
2017-01-13 23:17:31 +01:00
|
|
|
updatedProject.files.id(staleId).remove();
|
|
|
|
});
|
2017-03-02 20:38:29 +01:00
|
|
|
updatedProject.save((innerErr, savedProject) => {
|
2017-01-13 23:17:31 +01:00
|
|
|
if (innerErr) {
|
|
|
|
console.log(innerErr);
|
2017-02-22 20:29:35 +01:00
|
|
|
res.json({ success: false });
|
|
|
|
return;
|
2017-01-13 23:17:31 +01:00
|
|
|
}
|
2017-03-02 20:38:29 +01:00
|
|
|
res.json(savedProject);
|
2017-01-13 23:17:31 +01:00
|
|
|
});
|
2019-02-20 22:27:28 +01:00
|
|
|
} else {
|
|
|
|
res.json(updatedProject);
|
2017-01-13 23:17:31 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2016-06-19 00:33:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getProject(req, res) {
|
2018-02-07 22:00:09 +01:00
|
|
|
const projectId = req.params.project_id;
|
|
|
|
Project.findById(projectId)
|
2016-07-15 17:54:47 +02:00
|
|
|
.populate('user', 'username')
|
2018-02-07 22:00:09 +01:00
|
|
|
.exec((err, project) => { // eslint-disable-line
|
2016-07-15 17:54:47 +02:00
|
|
|
if (err) {
|
|
|
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
2018-02-07 22:00:09 +01:00
|
|
|
} else if (!project) {
|
|
|
|
Project.findOne({ slug: projectId })
|
2018-05-05 02:22:39 +02:00
|
|
|
.populate('user', 'username')
|
|
|
|
.exec((innerErr, projectBySlug) => {
|
|
|
|
if (innerErr || !projectBySlug) {
|
|
|
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
|
|
|
}
|
|
|
|
return res.json(projectBySlug);
|
|
|
|
});
|
2018-02-07 22:00:09 +01:00
|
|
|
} else {
|
|
|
|
return res.json(project);
|
2016-07-15 17:54:47 +02:00
|
|
|
}
|
|
|
|
});
|
2016-06-24 00:29:55 +02:00
|
|
|
}
|
2016-07-01 17:30:40 +02:00
|
|
|
|
2017-03-30 22:52:36 +02:00
|
|
|
function deleteFilesFromS3(files) {
|
2018-05-05 02:22:39 +02:00
|
|
|
deleteObjectsFromS3(files.filter((file) => {
|
|
|
|
if (file.url) {
|
2019-03-21 22:08:46 +01:00
|
|
|
if (!process.env.S3_DATE || (
|
|
|
|
process.env.S3_DATE &&
|
|
|
|
isBefore(new Date(process.env.S3_DATE), new Date(file.createdAt)))) {
|
2018-05-05 02:22:39 +02:00
|
|
|
return true;
|
2017-03-30 23:28:28 +02:00
|
|
|
}
|
2018-05-05 02:22:39 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
})
|
2017-07-19 19:56:52 +02:00
|
|
|
.map(file => getObjectKey(file.url)));
|
2017-03-30 22:52:36 +02:00
|
|
|
}
|
|
|
|
|
2016-10-12 20:24:53 +02:00
|
|
|
export function deleteProject(req, res) {
|
2017-02-22 20:29:35 +01:00
|
|
|
Project.findById(req.params.project_id, (findProjectErr, project) => {
|
2018-01-09 21:57:49 +01:00
|
|
|
if (!project.user.equals(req.user._id)) {
|
2017-02-22 20:29:35 +01:00
|
|
|
res.status(403).json({ success: false, message: 'Session does not match owner of project.' });
|
|
|
|
return;
|
2016-10-12 20:24:53 +02:00
|
|
|
}
|
2017-03-30 22:52:36 +02:00
|
|
|
deleteFilesFromS3(project.files);
|
2017-02-22 20:29:35 +01:00
|
|
|
Project.remove({ _id: req.params.project_id }, (removeProjectError) => {
|
|
|
|
if (removeProjectError) {
|
|
|
|
res.status(404).send({ message: 'Project with that id does not exist' });
|
|
|
|
return;
|
2017-01-13 23:17:31 +01:00
|
|
|
}
|
2017-02-22 20:29:35 +01:00
|
|
|
res.json({ success: true });
|
2017-01-13 23:17:31 +01:00
|
|
|
});
|
2016-10-12 20:24:53 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-11 17:37:43 +02:00
|
|
|
export function getProjectsForUserId(userId) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
Project.find({ user: userId })
|
2016-07-01 17:30:40 +02:00
|
|
|
.sort('-createdAt')
|
2016-08-10 00:45:59 +02:00
|
|
|
.select('name files id createdAt updatedAt')
|
2016-07-01 17:30:40 +02:00
|
|
|
.exec((err, projects) => {
|
2017-07-11 17:37:43 +02:00
|
|
|
if (err) {
|
|
|
|
console.log(err);
|
|
|
|
}
|
|
|
|
resolve(projects);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-01 06:08:11 +02:00
|
|
|
export function getProjectAsset(req, res) {
|
|
|
|
Project.findById(req.params.project_id)
|
|
|
|
.populate('user', 'username')
|
2017-10-16 05:27:05 +02:00
|
|
|
.exec((err, project) => { // eslint-disable-line
|
2017-06-01 06:08:11 +02:00
|
|
|
if (err) {
|
|
|
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
|
|
|
}
|
2017-11-27 21:14:50 +01:00
|
|
|
if (!project) {
|
|
|
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
|
|
|
}
|
2017-06-01 06:08:11 +02:00
|
|
|
|
2019-02-23 00:05:56 +01:00
|
|
|
const filePath = req.params[0];
|
|
|
|
const resolvedFile = resolvePathToFile(filePath, project.files);
|
|
|
|
if (!resolvedFile) {
|
2017-06-01 06:08:11 +02:00
|
|
|
return res.status(404).send({ message: 'Asset does not exist' });
|
|
|
|
}
|
2019-02-23 00:05:56 +01:00
|
|
|
if (!resolvedFile.url) {
|
|
|
|
return res.send(resolvedFile.content);
|
|
|
|
}
|
|
|
|
request({ method: 'GET', url: resolvedFile.url, encoding: null }, (innerErr, response, body) => {
|
2017-10-16 05:27:05 +02:00
|
|
|
if (innerErr) {
|
|
|
|
return res.status(404).send({ message: 'Asset does not exist' });
|
|
|
|
}
|
|
|
|
return res.send(body);
|
|
|
|
});
|
2017-06-01 06:08:11 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-11 17:37:43 +02:00
|
|
|
export function getProjectsForUserName(username) {
|
2017-10-16 05:27:05 +02:00
|
|
|
|
2017-07-11 17:37:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getProjects(req, res) {
|
|
|
|
if (req.user) {
|
|
|
|
getProjectsForUserId(req.user._id)
|
|
|
|
.then((projects) => {
|
2016-07-01 17:30:40 +02:00
|
|
|
res.json(projects);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// could just move this to client side
|
2017-02-22 20:29:35 +01:00
|
|
|
res.json([]);
|
2016-07-01 17:30:40 +02:00
|
|
|
}
|
|
|
|
}
|
2016-08-17 21:53:25 +02:00
|
|
|
|
|
|
|
export function getProjectsForUser(req, res) {
|
|
|
|
if (req.params.username) {
|
|
|
|
User.findOne({ username: req.params.username }, (err, user) => {
|
2017-01-06 19:08:03 +01:00
|
|
|
if (!user) {
|
2017-02-22 20:29:35 +01:00
|
|
|
res.status(404).json({ message: 'User with that username does not exist.' });
|
|
|
|
return;
|
2017-01-06 19:08:03 +01:00
|
|
|
}
|
2017-07-11 17:37:43 +02:00
|
|
|
Project.find({ user: user._id })
|
2017-01-24 23:20:40 +01:00
|
|
|
.sort('-createdAt')
|
|
|
|
.select('name files id createdAt updatedAt')
|
2017-02-22 20:29:35 +01:00
|
|
|
.exec((innerErr, projects) => res.json(projects));
|
2016-08-17 21:53:25 +02:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// could just move this to client side
|
2017-02-22 20:29:35 +01:00
|
|
|
res.json([]);
|
2016-08-17 21:53:25 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-02 19:08:53 +01:00
|
|
|
|
2018-02-19 21:21:47 +01:00
|
|
|
export function projectExists(projectId, callback) {
|
|
|
|
Project.findById(projectId, (err, project) => (
|
|
|
|
project ? callback(true) : callback(false)
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
export function projectForUserExists(username, projectId, callback) {
|
|
|
|
User.findOne({ username }, (err, user) => {
|
|
|
|
if (!user) {
|
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
2018-02-20 20:22:33 +01:00
|
|
|
Project.findOne({ _id: projectId, user: user._id }, (innerErr, project) => {
|
2018-02-20 20:16:58 +01:00
|
|
|
if (project) {
|
|
|
|
callback(true);
|
|
|
|
return;
|
|
|
|
}
|
2018-02-20 20:22:33 +01:00
|
|
|
Project.findOne({ slug: projectId, user: user._id }, (slugError, projectBySlug) => {
|
2018-02-20 20:16:58 +01:00
|
|
|
if (projectBySlug) {
|
|
|
|
callback(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
callback(false);
|
|
|
|
});
|
|
|
|
});
|
2018-02-19 21:21:47 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-19 19:56:52 +02:00
|
|
|
function bundleExternalLibs(project, zip, callback) {
|
|
|
|
const indexHtml = project.files.find(file => file.name === 'index.html');
|
|
|
|
let numScriptsResolved = 0;
|
|
|
|
let numScriptTags = 0;
|
|
|
|
|
|
|
|
function resolveScriptTagSrc(scriptTag, document) {
|
|
|
|
const path = scriptTag.src.split('/');
|
|
|
|
const filename = path[path.length - 1];
|
2018-05-05 02:59:43 +02:00
|
|
|
const { src } = scriptTag;
|
2017-07-19 19:56:52 +02:00
|
|
|
|
|
|
|
if (!isUrl(src)) {
|
|
|
|
numScriptsResolved += 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
request({ method: 'GET', url: src, encoding: null }, (err, response, body) => {
|
|
|
|
if (err) {
|
|
|
|
console.log(err);
|
|
|
|
} else {
|
|
|
|
zip.append(body, { name: filename });
|
|
|
|
scriptTag.src = filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
numScriptsResolved += 1;
|
|
|
|
if (numScriptsResolved === numScriptTags) {
|
|
|
|
indexHtml.content = serializeDocument(document);
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
jsdom.env(indexHtml.content, (innerErr, window) => {
|
|
|
|
const indexHtmlDoc = window.document;
|
|
|
|
const scriptTags = indexHtmlDoc.getElementsByTagName('script');
|
|
|
|
numScriptTags = scriptTags.length;
|
|
|
|
for (let i = 0; i < numScriptTags; i += 1) {
|
|
|
|
resolveScriptTagSrc(scriptTags[i], indexHtmlDoc);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-11-02 19:08:53 +01:00
|
|
|
function buildZip(project, req, res) {
|
|
|
|
const zip = archiver('zip');
|
|
|
|
const rootFile = project.files.find(file => file.name === 'root');
|
|
|
|
const numFiles = project.files.filter(file => file.fileType !== 'folder').length;
|
2018-05-05 02:59:43 +02:00
|
|
|
const { files } = project;
|
2016-11-02 19:08:53 +01:00
|
|
|
let numCompletedFiles = 0;
|
|
|
|
|
2017-01-24 23:20:40 +01:00
|
|
|
zip.on('error', (err) => {
|
2016-11-17 17:15:35 +01:00
|
|
|
res.status(500).send({ error: err.message });
|
2016-11-02 19:08:53 +01:00
|
|
|
});
|
|
|
|
|
2019-03-02 10:35:40 +01:00
|
|
|
const currentTime = format(new Date(), 'YYYY_MM_DD_HH_mm_ss');
|
2019-02-10 02:27:03 +01:00
|
|
|
project.slug = slugify(project.name, '_');
|
|
|
|
res.attachment(`${generateFileSystemSafeName(project.slug)}_${currentTime}.zip`);
|
2016-11-02 19:08:53 +01:00
|
|
|
zip.pipe(res);
|
|
|
|
|
|
|
|
function addFileToZip(file, path) {
|
|
|
|
if (file.fileType === 'folder') {
|
|
|
|
const newPath = file.name === 'root' ? path : `${path}${file.name}/`;
|
2017-02-22 20:29:35 +01:00
|
|
|
file.children.forEach((fileId) => {
|
2016-11-02 19:08:53 +01:00
|
|
|
const childFile = files.find(f => f.id === fileId);
|
|
|
|
(() => {
|
|
|
|
addFileToZip(childFile, newPath);
|
|
|
|
})();
|
|
|
|
});
|
2017-02-22 20:29:35 +01:00
|
|
|
} else if (file.url) {
|
|
|
|
request({ method: 'GET', url: file.url, encoding: null }, (err, response, body) => {
|
|
|
|
zip.append(body, { name: `${path}${file.name}` });
|
2016-11-02 19:08:53 +01:00
|
|
|
numCompletedFiles += 1;
|
|
|
|
if (numCompletedFiles === numFiles) {
|
|
|
|
zip.finalize();
|
|
|
|
}
|
2017-02-22 20:29:35 +01:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
zip.append(file.content, { name: `${path}${file.name}` });
|
|
|
|
numCompletedFiles += 1;
|
|
|
|
if (numCompletedFiles === numFiles) {
|
|
|
|
zip.finalize();
|
2016-11-02 19:08:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-19 19:56:52 +02:00
|
|
|
|
|
|
|
bundleExternalLibs(project, zip, () => {
|
|
|
|
addFileToZip(rootFile, '/');
|
|
|
|
});
|
2016-11-02 19:08:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export function downloadProjectAsZip(req, res) {
|
|
|
|
Project.findById(req.params.project_id, (err, project) => {
|
2016-11-17 17:15:35 +01:00
|
|
|
// save project to some path
|
2016-11-02 19:08:53 +01:00
|
|
|
buildZip(project, req, res);
|
|
|
|
});
|
|
|
|
}
|