Source: models/file.js

'use strict';

/**
 * Model for file metadata.
 *
 * @namespace File
 */
const thinky = require('../db');
const r = thinky.r;
const type = thinky.type;
const mlString = require('./helpers/ml-string');
const revision = require('./helpers/revision');

const User = require('./user');
const validLicenses = ['cc-0', 'cc-by', 'cc-by-sa', 'fair-use'];

/* eslint-disable newline-per-chained-call */ /* for schema readability */

const fileSchema = {
  id: type.string().uuid(4),
  name: type.string().max(512),
  description: mlString.getSchema(),
  uploadedBy: type.string().uuid(4),
  uploadedOn: type.date(),
  mimeType: type.string(),
  license: type.string().enum(validLicenses),
  // Provided by uploader: if not the author, who is?
  creator: mlString.getSchema(),
  // Provided by uploader: where does this file come from?
  source: mlString.getSchema(),
  // Uploaded files with incomplete metadata are stored in a separate directory
  completed: type.boolean().default(false),
  userCanDelete: type.virtual().default(false),
  userIsCreator: type.virtual().default(false)
};

/* eslint-enable newline-per-chained-call */ /* for schema readability */

Object.assign(fileSchema, revision.getSchema());
const File = thinky.createModel("files", fileSchema);

File.belongsTo(User, "uploader", "uploadedBy", "id");
File.ensureIndex("uploadedOn");

// NOTE: STATIC METHODS --------------------------------------------------------

// Standard handlers

File.createFirstRevision = revision.getFirstRevisionHandler(File);
File.getNotStaleOrDeleted = revision.getNotStaleOrDeletedGetHandler(File);
File.filterNotStaleOrDeleted = revision.getNotStaleOrDeletedFilterHandler(File);
File.getMultipleNotStaleOrDeleted = revision.getMultipleNotStaleOrDeletedHandler(File);

// Custom handlers

File.getStashedUpload = async function(userID, name) {
  const files = await File
    .filter({
      name,
      uploadedBy: userID,
      completed: false
    })
    .filter({ _revDeleted: false }, { default: true })
    .filter({ _oldRevOf: false }, { default: true });
  return files[0];
};

File.getValidLicenses = () => validLicenses.slice();

File.getFileFeed = async function({ offsetDate, limit = 10 } = {}) {
  let query = File;

  if (offsetDate && offsetDate.valueOf)
    query = query.between(r.minval, r.epochTime(offsetDate.valueOf() / 1000), {
      index: 'uploadedOn',
      rightBound: 'open' // Do not return previous record that exactly matches offset
    });

  query = query
    .filter({ _revDeleted: false }, { default: true })
    .filter({ _oldRevOf: false }, { default: true })
    .filter({ completed: true })
    .getJoin({ things: true, uploader: true })
    .orderBy(r.desc('uploadedOn'))
    .limit(limit + 1);

  const feed = {
    items: await query
  };

  // At least one additional document available, set offset for pagination
  if (feed.items.length == limit + 1) {
    feed.offsetDate = feed.items[limit - 1].uploadedOn;
    feed.items.pop();
  }
  return feed;

};

// NOTE: INSTANCE METHODS ------------------------------------------------------

// Standard handlers

File.define("newRevision", revision.getNewRevisionHandler(File));
File.define("deleteAllRevisions", revision.getDeleteAllRevisionsHandler(File));
File.define("populateUserInfo", populateUserInfo);

/**
 * Populate virtual permission fields in a File object with the rights of a
 * given user.
 *
 * @param {User} user
 *  the user whose permissions to check
 * @memberof File
 * @instance
 */
function populateUserInfo(user) {
  if (!user)
    return; // Permissions will be at their default value (false)

  this.userIsCreator = user.id === this.createdBy;
  this.userCanDelete = this.userIsCreator || user.isSuperUser || user.isSiteModerator || false;
}

module.exports = File;