fragments / src / model / fragment.js
fragment.js
Raw
// Use crypto.randomUUID() to create unique IDs, see:
// https://nodejs.org/api/crypto.html#cryptorandomuuidoptions
const { randomUUID } = require('crypto');
// Use https://www.npmjs.com/package/content-type to create/parse Content-Type headers
const contentType = require('content-type');
const logger = require('../logger');
const MarkdownIt = require('markdown-it');
const sharp = require('sharp');

// Functions for working with fragment metadata/data using our DB
const {
  readFragment,
  writeFragment,
  readFragmentData,
  writeFragmentData,
  listFragments,
  deleteFragment,
} = require('./data');

class Fragment {
  constructor({ id, ownerId, created, updated, type, size = 0 }) {
    if(!ownerId){
        throw new Error(`OwnerId of Fragment i required`);
    }
    if(!type){
        throw new Error(`Type of Fragment i required`);
    }

    this.id = id || randomUUID();
    this.ownerId = ownerId;

    if (typeof size !== 'number' || size < 0)
        throw new Error('size must be a positive number');
    
    this.size = size;

    if (!Fragment.isSupportedType(type))
        throw new Error(`${type} is an invalid type`);
    this.type = type;
    this.created = created || new Date().toString();
    this.updated = updated || new Date().toString();
    
  }

  /**
   * Get all fragments (id or full) for the given user
   * @param {string} ownerId user's hashed email
   * @param {boolean} expand whether to expand ids to full fragments
   * @returns Promise<Array<Fragment>>
   */
  static async byUser(ownerId, expand = false) {
    const result = await listFragments(ownerId, expand);
    return result;
  }

  /**
   * Gets a fragment for the user by the given id.
   * @param {string} ownerId user's hashed email
   * @param {string} id fragment's id
   * @returns Promise<Fragment>
   */
  static async byId(ownerId, id) {
    const fragment = await readFragment(ownerId, id);
    if (!fragment) {
      throw new Error(`Fragment ${id} not found!`);
    }
    return fragment;
  }

  /**
   * Delete the user's fragment data and metadata for the given id
   * @param {string} ownerId user's hashed email
   * @param {string} id fragment's id
   * @returns Promise<void>
   */
  static delete(ownerId, id) {
    return deleteFragment(ownerId, id);
  }

  /**
   * Saves the current fragment to the database
   * @returns Promise<void>
   */
  save() {
    this.updated = new Date().toString();
    return writeFragment(this);
  }

  /**
   * Gets the fragment's data from the database
   * @returns Promise<Buffer>
   */
  getData() {
    return readFragmentData(this.ownerId, this.id);
  }

  /**
   * Set's the fragment's data in the database
   * @param {Buffer} data
   * @returns Promise<void>
   */
  async setData(data) {
    if (!data) throw new Error('Data is empty');
    if (!Buffer.isBuffer(data)) throw new Error('Data is not buffer');

    this.size = Buffer.byteLength(data);
    await this.save();
    return await writeFragmentData(this.ownerId, this.id, data);
    
  }

  /**
   * Returns the mime type (e.g., without encoding) for the fragment's type:
   * "text/html; charset=utf-8" -> "text/html"
   * @returns {string} fragment's mime type (without encoding)
   */
  get mimeType() {
    const { type } = contentType.parse(this.type);
    return type;
  }

  /**
   * Returns true if this fragment is a text/* mime type
   * @returns {boolean} true if fragment's type is text/*
   */
  get isText() {
    const { type } = contentType.parse(this.type);
    return type == 'text/plain' ? true : false;
  }

  /**
   * Returns the formats into which this fragment type can be converted
   * @returns {Array<string>} list of supported mime types
   */
  get formats() {
    return [contentType.format({ type: 'text/plain' })];
  }

  /**
   * Check for the extension to see if it can be converted to a image type
   * @returns bool
   */
  checkImageExtension(ext2) {
    const imageTypes = ['.png', '.jpeg', '.webp', '.gif'];

    if (imageTypes.find((element) => element == ext2)) return true;

    return false;
  }

  /**
   * Check for the url and convert the result
   * @returns object
   */
  async convertData(data, ext2, inputType) {
    let convertedFragmentData = data;
    
    logger.debug({ ext2 }, 'extension');
    if (this.mimeType !== inputType) {
      const md = new MarkdownIt();
      try {
        switch (inputType) {
          case 'text/html':
            if (this.mimeType === 'text/markdown') {
              
        
              const dataData_h1Content = `# ${data.toString()}`;
              convertedFragmentData = md.render(dataData_h1Content);
              convertedFragmentData = Buffer.from(convertedFragmentData);
            }
					break;
          case 'image/jpeg':
            convertedFragmentData = await sharp(data).jpeg().toBuffer();
            break;
  
          case 'image/png':
            convertedFragmentData = await sharp(data).png().toBuffer();
            break;
  
          case 'image/webp':
            convertedFragmentData = await sharp(data).webp().toBuffer();
            break;
  
          case 'image/gif':
            convertedFragmentData = await sharp(data).gif().toBuffer();
            break;
  
          default:
            break;
        }
      } catch (err) {
        throw 'Error parsing image';
      }
    }
   
    //logger.debug({ convertedFragmentData }, 'After ToString');
    return convertedFragmentData;
  }

  /**
   * Returns true if we know how to work with this content type
   * @param {string} value a Content-Type value (e.g., 'text/plain' or 'text/plain: charset=utf-8')
   * @returns {boolean} true if we support this Content-Type (i.e., type/subtype)
   */
  static isSupportedType(value) {
    const validTypes = [
        'text/plain',
        'text/plain; charset=utf-8',
        'text/markdown',
        'text/html',
        'application/json',
        'image/png',
        'image/jpeg',
        'image/webp',
        'image/gif'
      ];
      const result =  validTypes.includes(value);
      return result;
  }
}

module.exports.Fragment = Fragment;