All files / server/domain-objects Project.js

97.78% Statements 44/45
87.5% Branches 14/16
87.5% Functions 7/8
97.73% Lines 43/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134              3x 3x                                             40x 40x   40x 52x 52x   52x 23x             23x       23x 23x 23x   29x           29x 1x 28x 23x   5x     29x         52x 35x       40x       17x           17x   17x 4x     5x   4x 4x   4x     13x       14x                 11x 11x   11x 8x 5x     8x   3x     6x 6x   6x    
import isPlainObject from 'lodash/isPlainObject';
import pick from 'lodash/pick';
import Project from '../models/project';
import createId from '../utils/createId';
import createApplicationErrorClass from '../utils/createApplicationErrorClass';
import createDefaultFiles from './createDefaultFiles';
 
export const FileValidationError = createApplicationErrorClass('FileValidationError');
export const ProjectValidationError = createApplicationErrorClass('ProjectValidationError');
 
/**
 * This converts between a mongoose Project model
 * and the public API Project object properties
 *
 */
export function toApi(model) {
  return {
    id: model.id,
    name: model.name,
  };
}
 
/**
 * Transforms a tree of files matching the APIs DirectoryContents
 * format into the data structure stored in mongodb
 *
 * - flattens the tree into an array of file/folders
 * - each file/folder gets a generated BSON-ID
 * - each folder has a `children` array containing the IDs of it's children
 */
function transformFilesInner(tree = {}, parentNode) {
  const files = [];
  const errors = [];
 
  Object.entries(tree).forEach(([name, params]) => {
    const id = createId();
    const isFolder = params.files != null;
 
    if (isFolder) {
      const folder = {
        _id: id,
        name,
        fileType: 'folder',
        children: [] // Initialise an empty folder
      };
 
      files.push(folder);
 
      // The recursion will return a list of child files/folders
      // It will also push the child's id into `folder.children`
      const subFolder = transformFilesInner(params.files, folder);
      files.push(...subFolder.files);
      errors.push(...subFolder.errors);
    } else {
      const file = {
        _id: id,
        name,
        fileType: 'file'
      };
 
      if (typeof params.url === 'string') {
        file.url = params.url;
      } else if (typeof params.content === 'string') {
        file.content = params.content;
      } else {
        errors.push({ name, message: 'missing \'url\' or \'content\'' });
      }
 
      files.push(file);
    }
 
    // Push this child's ID onto it's parent's list
    // of children
    if (parentNode != null) {
      parentNode.children.push(id);
    }
  });
 
  return { files, errors };
}
 
export function transformFiles(tree = {}) {
  const withRoot = {
    root: {
      files: tree
    }
  };
 
  const { files, errors } = transformFilesInner(withRoot);
 
  if (errors.length > 0) {
    const message = `${errors.length} files failed validation. See error.files for individual errors.
    
    Errors:
      ${errors.map(e => `* ${e.name}: ${e.message}`).join('\n')}
`;
    const error = new FileValidationError(message);
    error.files = errors;
 
    throw error;
  }
 
  return files;
}
 
export function containsRootHtmlFile(tree) {
  return Object.keys(tree).find(name => /\.html$/.test(name)) != null;
}
 
/**
 * This converts between the public API's Project object
 * properties and a mongoose Project model
 *
 */
export function toModel(object) {
  let files = [];
  let tree = object.files;
 
  if (isPlainObject(tree)) {
    if (!containsRootHtmlFile(tree)) {
      tree = Object.assign(createDefaultFiles(), tree);
    }
 
    files = transformFiles(tree);
  } else {
    throw new FileValidationError('\'files\' must be an object');
  }
 
  const projectValues = pick(object, ['user', 'name', 'slug']);
  projectValues.files = files;
 
  return new Project(projectValues);
}