<commit_before>/*
  Model: Person
  =============

  A Person is a sub type of an Entity. Each person is qualified with a distinct slug.
  That is, we can say that a person is unique by checking its neo4J label and its slug.
  The previous doi has been preserved for migration purposes.
  Its neo4j json representation should be at least:
  {
    slug: 'simone-veil',
    uri:  'person/simone-veil',
    props: {
      slug: 'simone-veil',
      first_name: 'Simone',
      last_name: 'Veil',
      name: 'Simone Veil',
      doi: '',
      birth_date: '1927-07-13',
      birth_time: '-1355961180',
      birth_place: 'Nice, Provence, France',
      thumbnail: 'http://commons.w..',
      viaf_id: '120689047',
      wiki_id: 'Simone_Veil'
      languages: [ 'en' ],
      abstract_en: '...'
    }
  }

*/
var settings  = require('../settings'),
    helpers   = require('../helpers.js'),
    models    = require('../helpers/models'),
    neo4j     = require('seraph')(settings.neo4j.host),
    queries   = require('decypher')('./queries/person.cyp'),

    _         = require('lodash'),
    async     = require('async'),
    COUNTRIES = require('../ISO_3166-1');

module.exports = {
  /*
    Given a cypher Person with some activities on it,
    return the clean model. The person shoulkd have some relationships and activities. It should be linked to activity model scratch as well
  */
  scratch: function(person) {
    var per  = {
          slug: person.slug,
          uri:  person.uri,
          props: person.props
        },
        rels = _.groupBy(person.rels, 'end');
    per.activities = _.values(_.indexBy(person.activities.map(function (d) {
      var _d = {
        slug:           d.slug,
        country_code:   d.country,
        description_fr: d.description_fr,
        description_en: d.description_en,
        timeline: _.map(rels[d.id], 'properties')
      };

      _d.country_code = d.country;
      _d.country = d.country.length? _.find(COUNTRIES, {
        code: d.country
      }).value: '';
      return _d;
    }), 'slug'));
    return per
  },
  /*
    Get a single person out of the list by person slug.
    It come with activitites related information
  */
  get: function (person, next) {
    neo4j.query(queries.get_person, {
      slug: person.slug
    }, function (err, nodes) {
      if(err) {
        next(err);
        return
      }
      if(!nodes.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      var per  = {
            slug: nodes[0].slug,
            uri:  nodes[0].uri,
            props: nodes[0].props
          };
      console.log(nodes[0].activities)
      // re write activities
      per.nationalities = nodes[0].nationalities.filter(function (d) {
        return d.id;
      }).map(function (d) {
        var _d =  {
          slug: d.slug,
          uri: d.uri,
          props: d.props,
          reference: d.rel? (d.rel.reference || ''): '',
          timeline: d.rel? d.rel.properties : [],
          country: _.find(COUNTRIES, {code: d.slug})
        };
        return _d
      });

      per.activities = _.sortByOrder(_.map(_.filter(nodes[0].activities, 'id'), function (d) {
        var _d =  _.assign({
          slug: d.slug,
          uri: d.uri,
          props: d.props,
          institutions: _.filter(d.institutions || [], 'id')
        }, (d.rel? d.rel.properties : {}));

        _d.props.country_code = ''+d.props.country;
        _d.props.country = _.find(COUNTRIES, {code: d.props.country}).value;

        return _d
      }),['start_date', 'end_date'], ['asc', 'asc']);

      //per.activities = per.activities ['start_date', 'end_date'],['desc', 'desc']);

      next(null, per)
    });
  },

  getMany: function(params, next) {
    models.getMany({
      queries:{
        items: queries.get_persons,
        total_count: queries.count_persons
      },
      params: params
    }, next);
  },

  getRelatedMedia: function(person, params, next) {
    var params = _.assign({
      offset: 0,
      limit: 10
    }, params, {
      slug:  person.slug
    });

    models.getMany({
      queries:{
        items: queries.get_related_medias,
        total_count: queries.count_related_medias
      },
      params: params
    }, next);
  },

  getRelatedPersons: function(person, params, next) {
    async.parallel({
      by_activity: function(callback) {
        neo4j.query(queries.get_related_persons_by_activity, {
          slug:   person.slug,
          offset: params.offset,
          limit:  params.limit
        }, function(err, nodes) {
          if(err)
            return callback(err);
          return callback(err, nodes);
        })
      },
      by_institution: function(callback) {
        neo4j.query(queries.get_related_persons_by_institution, {
          slug:   person.slug,
          offset: params.offset,
          limit:  params.limit
        }, function(err, nodes) {
          if(err)
            return callback(err);
          return callback(err, nodes);
        })
      }
    }, function (err, results) {
      if(err)
        return next(err);
      var persons = _.values(_.groupBy(results.by_activity.concat(results.by_institution), 'slug'))
            .map(function (d) {
              var person = {
                slug: d[0].slug,
                uri: 'person/' + d[0].slug,
                props: d[0].props,
                score: _.sum(d, function(e) { // calculate also in term of time proximity
                  return e.activities? e.amount*2: e.amount;
                }),
                activities: _.flatten(_.compact(_.map(d, 'activities'))),
                institution: _.flatten(_.compact(_.map(d, 'institutions')))
              };
              return person;
            });

      next(null, _.sortByOrder(persons, 'score', 'desc'));
    })

  },

  /*
    Create a person, take into account the unicity of the person as well.
  */
  create: function(person, next) {
    if(!person.slug)
      person.slug = helpers.extract.smartSlug(person.first_name + ' ' + person.last_name);
    // if(!person.slug) { // get the first empty slug...
    //   neo4j.query(queries.get_slugs, function (err, slugs){
    //     // take the very first, non existing slug, then create it.
    //     if(err) {
    //       return next(err)
    //     }
    //     var slugs = _.map(slugs, 'slug'),
    //         slug = helpers.extract.smartSlug(person.first_name + ' ' + person.last_name);
    //     console.log('test with', slug, slugs.indexOf(slug))
    //     if(slugs.indexOf(slug) !== -1) {
    //       slug = slug + person.birth_date.substr(0,4);
    //       console.log('test twice with', slug, person.birth_date.substr(0,4),slugs.indexOf(slug))
    //     }

    //     module.exports.create(_.assign(person, {slug: slug}), next);
    //   });
    //   return;
    // }
    var now = helpers.now(),
        query = helpers.cypher.query(queries.create_person, person);
    // i you have provided a slugs that means that is has to be unique...
    neo4j.query(query, _.assign(person, {
      creation_date: now.date,
      creation_time: now.time
    }), function (err, nodes) {
      if(err)
        return next(err);
      next(null, nodes[0]);
    });
  },

  merge: function(properties, next) {
    var now = helpers.now(),
        query = helpers.cypher.query(queries.merge_person, properties);

    if(_.isEmpty(properties.name_search)) {
      properties.name_search = (properties.name || '').toLowerCase(); // lowercase true
    };
    // birth_time
    if(properties.birth_date && !properties.birth_time) {
      properties.birth_time = helpers.reconcileDate(properties.birth_date, 'YYYY-MM-DD').time
      properties.birth_date = helpers.reconcileDate(properties.birth_date, 'YYYY-MM-DD').date
    }
    // death_time
    if(properties.death_date && !properties.death_time) {
      properties.death_time = helpers.reconcileDate(properties.death_date, 'YYYY-MM-DD').time
      properties.death_date = helpers.reconcileDate(properties.death_date, 'YYYY-MM-DD').date
    }
    // create name_search if there isn't any
    neo4j.query(query, _.assign({}, properties, {
      creation_date: now.date,
      creation_time: now.time
    }), function (err, node) {
      if(err) {
        next(err);
        return;
      }
      if(!node.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      next(null, node[0]);
    })
  },

  discover: function(person, next) {
    // collect all the activities, then find stuff

    neo4j.query(queries.get_person, person, function (err, nodes) {
      if(err) {
        next(err);
        return;
      }
      if(!nodes.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      var node = nodes[0];

      var contents = node.activities.map(function(d) {
        return d.description_en;
      }).join('; ')
      console.log(node.props.name + ' ' + contents)
      helpers.yagoaida({
        contents: contents
      }, function (err, entities) {
        next(null, node);
      })
    })

  },

  // createNationality: function(person, next) {
  //   var now = helpers.now(),
  //       query = helpers.cypher.query(queries.merge_person, properties);

  // },
  /*
    Placeholder for real person. It uses name and original_slug to
    prepare a person.
    To find incomplete person:
    MATCH (p:person) WHERE not(has(p.slug)) RETURN p
  */
  merge_incomplete: function(properties, next) {
    var now = helpers.now(),
        query = helpers.cypher.query(queries.merge_incomplete_person, properties);
    neo4j.query(query, {
      original_slug: properties.original_slug,
      name: properties.name,
      doi: properties.doi,
      dois: properties.dois,
      languages: properties.languages,
      creation_date: now.date,
      creation_time: now.time
    }, function (err, node) {
      if(err) {
        next(err);
        return;
      }
      if(!node.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      next(null, node[0]);
    })
  },

  remove: function(person, next) {
    neo4j.query(queries.remove_person, {
      slug: person.slug
    }, function (err, node) {
      if(err) {
        next(err);
        return;
      }
      next(null);
    })
  }
};<commit_msg>fix activity item in related person api<commit_after>/*
  Model: Person
  =============

  A Person is a sub type of an Entity. Each person is qualified with a distinct slug.
  That is, we can say that a person is unique by checking its neo4J label and its slug.
  The previous doi has been preserved for migration purposes.
  Its neo4j json representation should be at least:
  {
    slug: 'simone-veil',
    uri:  'person/simone-veil',
    props: {
      slug: 'simone-veil',
      first_name: 'Simone',
      last_name: 'Veil',
      name: 'Simone Veil',
      doi: '',
      birth_date: '1927-07-13',
      birth_time: '-1355961180',
      birth_place: 'Nice, Provence, France',
      thumbnail: 'http://commons.w..',
      viaf_id: '120689047',
      wiki_id: 'Simone_Veil'
      languages: [ 'en' ],
      abstract_en: '...'
    }
  }

*/
var settings  = require('../settings'),
    helpers   = require('../helpers.js'),
    models    = require('../helpers/models'),
    neo4j     = require('seraph')(settings.neo4j.host),
    queries   = require('decypher')('./queries/person.cyp'),

    _         = require('lodash'),
    async     = require('async'),
    COUNTRIES = require('../ISO_3166-1');

module.exports = {
  /*
    Given a cypher Person with some activities on it,
    return the clean model. The person shoulkd have some relationships and activities. It should be linked to activity model scratch as well
  */
  scratch: function(person) {
    var per  = {
          slug: person.slug,
          uri:  person.uri,
          props: person.props
        },
        rels = _.groupBy(person.rels, 'end');
    per.activities = _.values(_.indexBy(person.activities.map(function (d) {
      var _d = {
        slug:           d.slug,
        country_code:   d.country,
        description_fr: d.description_fr,
        description_en: d.description_en,
        timeline: _.map(rels[d.id], 'properties')
      };

      _d.country_code = d.country;
      _d.country = d.country.length? _.find(COUNTRIES, {
        code: d.country
      }).value: '';
      return _d;
    }), 'slug'));
    return per
  },
  /*
    Get a single person out of the list by person slug.
    It come with activitites related information
  */
  get: function (person, next) {
    neo4j.query(queries.get_person, {
      slug: person.slug
    }, function (err, nodes) {
      if(err) {
        next(err);
        return
      }
      if(!nodes.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      var per  = {
            slug: nodes[0].slug,
            uri:  nodes[0].uri,
            props: nodes[0].props
          };
      console.log(nodes[0].activities)
      // re write activities
      per.nationalities = nodes[0].nationalities.filter(function (d) {
        return d.id;
      }).map(function (d) {
        var _d =  {
          slug: d.slug,
          uri: d.uri,
          props: d.props,
          reference: d.rel? (d.rel.reference || ''): '',
          timeline: d.rel? d.rel.properties : [],
          country: _.find(COUNTRIES, {code: d.slug})
        };
        return _d
      });

      per.activities = _.sortByOrder(_.map(_.filter(nodes[0].activities, 'id'), function (d) {
        var _d =  _.assign({
          slug: d.slug,
          uri: d.uri,
          props: d.props,
          institutions: _.filter(d.institutions || [], 'id')
        }, (d.rel? d.rel.properties : {}));

        _d.props.country_code = ''+d.props.country;
        _d.props.country = _.find(COUNTRIES, {code: d.props.country}).value;

        return _d
      }),['start_date', 'end_date'], ['asc', 'asc']);

      //per.activities = per.activities ['start_date', 'end_date'],['desc', 'desc']);

      next(null, per)
    });
  },

  getMany: function(params, next) {
    models.getMany({
      queries:{
        items: queries.get_persons,
        total_count: queries.count_persons
      },
      params: params
    }, next);
  },

  getRelatedMedia: function(person, params, next) {
    var params = _.assign({
      offset: 0,
      limit: 10
    }, params, {
      slug:  person.slug
    });

    models.getMany({
      queries:{
        items: queries.get_related_medias,
        total_count: queries.count_related_medias
      },
      params: params
    }, next);
  },

  getRelatedPersons: function(person, params, next) {
    async.parallel({
      by_activity: function(callback) {
        neo4j.query(queries.get_related_persons_by_activity, {
          slug:   person.slug,
          offset: params.offset,
          limit:  params.limit
        }, function(err, nodes) {
          if(err)
            return callback(err);
          return callback(err, nodes);
        })
      },
      by_institution: function(callback) {
        neo4j.query(queries.get_related_persons_by_institution, {
          slug:   person.slug,
          offset: params.offset,
          limit:  params.limit
        }, function(err, nodes) {
          if(err)
            return callback(err);
          return callback(err, nodes);
        })
      }
    }, function (err, results) {
      if(err)
        return next(err);
      var persons = _.values(_.groupBy(results.by_activity.concat(results.by_institution), 'slug'))
            .map(function (d) {
              var person = {
                slug: d[0].slug,
                uri: 'person/' + d[0].slug,
                props: d[0].props,
                score: _.sum(d, function(e) { // calculate also in term of time proximity
                  return e.activities? e.amount*2: e.amount;
                }),
                activities: _.flatten(_.compact(_.map(d, 'activities'))).map(function (d) {
                  var _d =  _.assign({
                    slug: d.slug,
                    uri: d.uri,
                    props: d.props,
                    institutions: _.filter(d.institutions || [], 'id')
                  }, (d.rel? d.rel.properties : {}));

                  _d.props.country_code = ''+d.props.country;
                  _d.props.country = _.find(COUNTRIES, {code: d.props.country}).value;
                  return _d;
                }),
                institution: _.flatten(_.compact(_.map(d, 'institutions')))
              };
              return person;
            });

      next(null, _.sortByOrder(persons, 'score', 'desc'));
    })

  },

  /*
    Create a person, take into account the unicity of the person as well.
  */
  create: function(person, next) {
    if(!person.slug)
      person.slug = helpers.extract.smartSlug(person.first_name + ' ' + person.last_name);
    // if(!person.slug) { // get the first empty slug...
    //   neo4j.query(queries.get_slugs, function (err, slugs){
    //     // take the very first, non existing slug, then create it.
    //     if(err) {
    //       return next(err)
    //     }
    //     var slugs = _.map(slugs, 'slug'),
    //         slug = helpers.extract.smartSlug(person.first_name + ' ' + person.last_name);
    //     console.log('test with', slug, slugs.indexOf(slug))
    //     if(slugs.indexOf(slug) !== -1) {
    //       slug = slug + person.birth_date.substr(0,4);
    //       console.log('test twice with', slug, person.birth_date.substr(0,4),slugs.indexOf(slug))
    //     }

    //     module.exports.create(_.assign(person, {slug: slug}), next);
    //   });
    //   return;
    // }
    var now = helpers.now(),
        query = helpers.cypher.query(queries.create_person, person);
    // i you have provided a slugs that means that is has to be unique...
    neo4j.query(query, _.assign(person, {
      creation_date: now.date,
      creation_time: now.time
    }), function (err, nodes) {
      if(err)
        return next(err);
      next(null, nodes[0]);
    });
  },

  merge: function(properties, next) {
    var now = helpers.now(),
        query = helpers.cypher.query(queries.merge_person, properties);

    if(_.isEmpty(properties.name_search)) {
      properties.name_search = (properties.name || '').toLowerCase(); // lowercase true
    };
    // birth_time
    if(properties.birth_date && !properties.birth_time) {
      properties.birth_time = helpers.reconcileDate(properties.birth_date, 'YYYY-MM-DD').time
      properties.birth_date = helpers.reconcileDate(properties.birth_date, 'YYYY-MM-DD').date
    }
    // death_time
    if(properties.death_date && !properties.death_time) {
      properties.death_time = helpers.reconcileDate(properties.death_date, 'YYYY-MM-DD').time
      properties.death_date = helpers.reconcileDate(properties.death_date, 'YYYY-MM-DD').date
    }
    // create name_search if there isn't any
    neo4j.query(query, _.assign({}, properties, {
      creation_date: now.date,
      creation_time: now.time
    }), function (err, node) {
      if(err) {
        next(err);
        return;
      }
      if(!node.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      next(null, node[0]);
    })
  },

  discover: function(person, next) {
    // collect all the activities, then find stuff

    neo4j.query(queries.get_person, person, function (err, nodes) {
      if(err) {
        next(err);
        return;
      }
      if(!nodes.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      var node = nodes[0];

      var contents = node.activities.map(function(d) {
        return d.description_en;
      }).join('; ')
      console.log(node.props.name + ' ' + contents)
      helpers.yagoaida({
        contents: contents
      }, function (err, entities) {
        next(null, node);
      })
    })

  },

  // createNationality: function(person, next) {
  //   var now = helpers.now(),
  //       query = helpers.cypher.query(queries.merge_person, properties);

  // },
  /*
    Placeholder for real person. It uses name and original_slug to
    prepare a person.
    To find incomplete person:
    MATCH (p:person) WHERE not(has(p.slug)) RETURN p
  */
  merge_incomplete: function(properties, next) {
    var now = helpers.now(),
        query = helpers.cypher.query(queries.merge_incomplete_person, properties);
    neo4j.query(query, {
      original_slug: properties.original_slug,
      name: properties.name,
      doi: properties.doi,
      dois: properties.dois,
      languages: properties.languages,
      creation_date: now.date,
      creation_time: now.time
    }, function (err, node) {
      if(err) {
        next(err);
        return;
      }
      if(!node.length) {
        next(helpers.IS_EMPTY);
        return;
      }
      next(null, node[0]);
    })
  },

  remove: function(person, next) {
    neo4j.query(queries.remove_person, {
      slug: person.slug
    }, function (err, node) {
      if(err) {
        next(err);
        return;
      }
      next(null);
    })
  }
};<|endoftext|>


<commit_before>// A report is a single post/comment/article or other chunk of data from a source.
// This class is responsible for executing ReportQuerys.
'use strict';

var database = require('../lib/database');
var mongoose = database.mongoose;
var Schema = mongoose.Schema;
var SchemaTypes = mongoose.SchemaTypes;
var logger = require('../lib/logger');
var SMTCTag = require('../models/tag');
var { addPost, removePost } = require('../lib/comments');

var schema = new Schema({
  authoredAt: Date,
  fetchedAt: Date,
  storedAt: { type: Date, index: true },
  content: { type: String, index: true },
  author: { type: String, index: true },
  url: String,
  metadata: Schema.Types.Mixed,
  tags: { type: [String], default: [] },
  smtcTags: {type: [{type: SchemaTypes.ObjectId, ref: 'SMTCTag'}], default: []},
  read: { type: Boolean, default: false, required: true, index: true },
  flagged: { type: Boolean, default: false, required: true, index: true },
  _sources: [{ type: String, ref: 'Source', index: true }],
  _media: { type: [String], index: true },
  _sourceNicknames: [String],
  _incident: { type: String, ref: 'Incident', index: true },
  checkedOutBy: { type: Schema.ObjectId, ref: 'User', index: true },
  checkedOutAt: { type: Date, index: true },
  commentTo: { type: Schema.ObjectId, ref: 'Report', index: true },
});

// Add fulltext index to the `content` field.
schema.index({ content: 'text' });
schema.path('_incident').set(function(_incident) {
  this._prevIncident = this._incident;
  return _incident;
});

schema.pre('save', function(next) {
  if (this.isNew) {
    this._wasNew = true;
    // Set default storedAt.
    if (!this.storedAt) this.storedAt = new Date();

  } else {
    // Capture updates before saving report
    if (this.isModified('_incident')) {
      this._incidentWasModified = true;
    }

  }
  next();
});

// Emit information about updates after saving report
schema.post('save', function() {
  if (this._wasNew) schema.emit('report:new', { _id: this._id.toString() });
  if (!this._wasNew) schema.emit('report:updated', this);
  if (this._incidentWasModified) {
    schema.emit('change:incident', this._prevIncident, this._incident);
  }
});

schema.methods.toggleFlagged = function(flagged) {
  this.flagged = flagged;

  if (flagged) {
    this.read = true;
  }
};

schema.methods.toggleRead = function(read) {
  this.read = read;
};

schema.methods.addSMTCTag = function(smtcTagId, callback) {
  // TODO: Use Functional Programming
  // ML This finds the smtcTag to add (if it doesn't exists) then add it.
  let isRepeat = false;
  this.smtcTags.forEach(function(tag) {
    if(smtcTagId === tag.toString()) {
      isRepeat = true;
    }
  });
  if (isRepeat === false) {
    this.smtcTags.push({_id: smtcTagId});

    if (!this.commentTo) {
      SMTCTag.findById(smtcTagId, (err, tag) => {
        if (err) {
          logger.error(err);
        }
        if (tag.isCommentTag) {
          addPost(this.url, callback)
        }
      });
      return;
    }
  }
  callback();
}

schema.methods.removeSMTCTag = function(smtcTagId, callback) {
  // TODO: Use Functional Programming
  // ML This finds the smtcTag to remove (if it exists) then remove it.
  if (this.smtcTags) {
    let fndIndex = -1;
    this.smtcTags.forEach(function(tag, index) {
      let string = tag.toString();
      if (smtcTagId === tag.toString()) {
        fndIndex = index;
      }
    })
    if (fndIndex !== -1) {
      this.smtcTags.splice(fndIndex, 1);

      if (!this.commentTo) {
        SMTCTag.findById(smtcTagId, (err, tag) => {
          if (err) {
            logger.error(err);
          }
          if (tag.isCommentTag) {
            removePost(this.url, callback)
          }
        });
        return;
      }
    }
  }
  callback();
}

schema.methods.clearSMTCTags = function(callback) {

  const cb = () => {
    this.smtcTags = [];
    callback();
  }

  if (!this.commentTo) {
    var remaining = this.smtcTags.length;
    this.smtcTags.forEach((tag) => {
      const tagId = tag.toString();
      this.removeSMTCTag(tagId, (err) => {
        if (err) {
          logger.error(err);
        }
        if (--remaining === 0) {
          cb();
        }
      });
    });
    return;
  }
  cb();
}

var Report = mongoose.model('Report', schema);

SMTCTag.schema.on('tag:removed', function(id) {
  Report.find({smtcTags: id}, function(err, reports) {
    if (err) {
      logger.error(err);
    }
    reports.forEach(function(report) {
      report.removeSMTCTag(id, () => {
        report.save();
      })
    });
  });
})

// queryReports reports based on passed query data
Report.queryReports = function(query, page, callback) {
  if (typeof query === 'function') return Report.findPage(query);
  if (typeof page === 'function') {
    callback = page;
    page = 0;
  }
  if (page < 0) page = 0;

  var filter = query.toMongooseFilter();

  // Re-set search timestamp
  query.since = new Date();

  Report.findSortedPage(filter, page, callback);
};

Report.findSortedPage = function(filter, page, callback) {
  Report.findPage(filter, page, { sort: '-storedAt' }, function(err, reports) {
    if (err) return callback(err);
    callback(null, reports);
  });
};

module.exports = Report;
<commit_msg>feat: only send API request for tagged CT reports<commit_after>// A report is a single post/comment/article or other chunk of data from a source.
// This class is responsible for executing ReportQuerys.
'use strict';

var database = require('../lib/database');
var mongoose = database.mongoose;
var Schema = mongoose.Schema;
var SchemaTypes = mongoose.SchemaTypes;
var logger = require('../lib/logger');
var SMTCTag = require('../models/tag');
var { addPost, removePost } = require('../lib/comments');

var schema = new Schema({
  authoredAt: Date,
  fetchedAt: Date,
  storedAt: { type: Date, index: true },
  content: { type: String, index: true },
  author: { type: String, index: true },
  url: String,
  metadata: Schema.Types.Mixed,
  tags: { type: [String], default: [] },
  smtcTags: {type: [{type: SchemaTypes.ObjectId, ref: 'SMTCTag'}], default: []},
  read: { type: Boolean, default: false, required: true, index: true },
  flagged: { type: Boolean, default: false, required: true, index: true },
  _sources: [{ type: String, ref: 'Source', index: true }],
  _media: { type: [String], index: true },
  _sourceNicknames: [String],
  _incident: { type: String, ref: 'Incident', index: true },
  checkedOutBy: { type: Schema.ObjectId, ref: 'User', index: true },
  checkedOutAt: { type: Date, index: true },
  commentTo: { type: Schema.ObjectId, ref: 'Report', index: true },
});

// Add fulltext index to the `content` field.
schema.index({ content: 'text' });
schema.path('_incident').set(function(_incident) {
  this._prevIncident = this._incident;
  return _incident;
});

schema.pre('save', function(next) {
  if (this.isNew) {
    this._wasNew = true;
    // Set default storedAt.
    if (!this.storedAt) this.storedAt = new Date();

  } else {
    // Capture updates before saving report
    if (this.isModified('_incident')) {
      this._incidentWasModified = true;
    }

  }
  next();
});

// Emit information about updates after saving report
schema.post('save', function() {
  if (this._wasNew) schema.emit('report:new', { _id: this._id.toString() });
  if (!this._wasNew) schema.emit('report:updated', this);
  if (this._incidentWasModified) {
    schema.emit('change:incident', this._prevIncident, this._incident);
  }
});

schema.methods.toggleFlagged = function(flagged) {
  this.flagged = flagged;

  if (flagged) {
    this.read = true;
  }
};

schema.methods.toggleRead = function(read) {
  this.read = read;
};

schema.methods.addSMTCTag = function(smtcTagId, callback) {
  // TODO: Use Functional Programming
  // ML This finds the smtcTag to add (if it doesn't exists) then add it.
  let isRepeat = false;
  this.smtcTags.forEach(function(tag) {
    if(smtcTagId === tag.toString()) {
      isRepeat = true;
    }
  });
  if (isRepeat === false) {
    this.smtcTags.push({_id: smtcTagId});

    if (!this.commentTo && this.media[0] === 'crowdtangle') {
      SMTCTag.findById(smtcTagId, (err, tag) => {
        if (err) {
          logger.error(err);
        }
        if (tag.isCommentTag) {
          addPost(this.url, callback)
        }
      });
      return;
    }
  }
  callback();
}

schema.methods.removeSMTCTag = function(smtcTagId, callback) {
  // TODO: Use Functional Programming
  // ML This finds the smtcTag to remove (if it exists) then remove it.
  if (this.smtcTags) {
    let fndIndex = -1;
    this.smtcTags.forEach(function(tag, index) {
      let string = tag.toString();
      if (smtcTagId === tag.toString()) {
        fndIndex = index;
      }
    })
    if (fndIndex !== -1) {
      this.smtcTags.splice(fndIndex, 1);

      if (!this.commentTo && this.media[0] === 'crowdtangle') {
        SMTCTag.findById(smtcTagId, (err, tag) => {
          if (err) {
            logger.error(err);
          }
          if (tag.isCommentTag) {
            removePost(this.url, callback)
          }
        });
        return;
      }
    }
  }
  callback();
}

schema.methods.clearSMTCTags = function(callback) {

  const cb = () => {
    this.smtcTags = [];
    callback();
  }

  if (!this.commentTo) {
    var remaining = this.smtcTags.length;
    this.smtcTags.forEach((tag) => {
      const tagId = tag.toString();
      this.removeSMTCTag(tagId, (err) => {
        if (err) {
          logger.error(err);
        }
        if (--remaining === 0) {
          cb();
        }
      });
    });
    return;
  }
  cb();
}

var Report = mongoose.model('Report', schema);

SMTCTag.schema.on('tag:removed', function(id) {
  Report.find({smtcTags: id}, function(err, reports) {
    if (err) {
      logger.error(err);
    }
    reports.forEach(function(report) {
      report.removeSMTCTag(id, () => {
        report.save();
      })
    });
  });
})

// queryReports reports based on passed query data
Report.queryReports = function(query, page, callback) {
  if (typeof query === 'function') return Report.findPage(query);
  if (typeof page === 'function') {
    callback = page;
    page = 0;
  }
  if (page < 0) page = 0;

  var filter = query.toMongooseFilter();

  // Re-set search timestamp
  query.since = new Date();

  Report.findSortedPage(filter, page, callback);
};

Report.findSortedPage = function(filter, page, callback) {
  Report.findPage(filter, page, { sort: '-storedAt' }, function(err, reports) {
    if (err) return callback(err);
    callback(null, reports);
  });
};

module.exports = Report;
<|endoftext|>


<commit_before>var database = require('../lib/database');
var mongoose = database.mongoose;
var textSearch = require('mongoose-text-search');
var Source = require('./source');
var Query = require('./query');
var _ = require('underscore');

var schema = new mongoose.Schema({
  authoredAt: Date,
  fetchedAt: Date,
  storedAt: Date,
  content: String,
  author: String,
  status: String,
  url: String,
  _source: {type: String, ref: 'Source'},
  _sourceType: String,
  _sourceNickname: String
});

// Give the report schema text search capabilities
schema.plugin(textSearch);
// Add a text index to the `content` field
schema.index({content: 'text'});

schema.pre('save', function(next) {
  if (this.isNew) {
    this._wasNew = true;
    this.storedAt = new Date();
  }
  next();
});

schema.post('save', function() {
  if (this._wasNew) schema.emit('report:save', {_id: this._id.toString()});
  else if (this.isModified('status')) schema.emit('report:status', {_id: this._id.toString(), status: this.status});
});

var Report = mongoose.model('Report', schema);

// Find reports using pagination
Report.findPage = function(filters, page, callback) {
  if (typeof filters === 'function') {
    callback = filters;
    filters = {};
    page = 0;
  }
  if (typeof page === 'function') {
    callback = page;
    page = 0;
  }
  if (page < 0) page = 0;
  var limit = 25;
  Report.count(filters, function(err, count) {
    if (err) return callback(err);
    var result = {total: count};
    Report.find(filters, null, {limit: limit, skip: page * limit, sort: '-storedAt'}, function(err, reports) {
      if (err) return callback(err);
      result.results = reports;
      callback(null, result);
    });
  });
};

// Query reports based on passed query data
Report.queryReports = function(query, page, callback) {
  if (typeof query === 'function') return Report.findPage(query);
  if (query instanceof Query) query = query.normalize();
  if (typeof page === 'function') {
    callback = page;
    page = 0;
  }
  if (page < 0) page = 0;

  query.limit = 100;
  query.filter = {};

  // Determine status filter
  if (query.status) {
    query.filter.status = {};
    if (query.status === 'assigned') query.filter.status.$exists = true;
    else if (query.status === 'unassigned') query.filter.status.$exists = false;
    else query.filter.status = query.status;
  }

  // Determine inclusive date filters
  if (query.after || query.before) {
    query.filter.storedAt = {};
    if (query.after) query.filter.storedAt.$gte = query.after;
    if (query.before) query.filter.storedAt.$lte = query.before;
  }

  // Convert sourceId and sourceType for Report compatibility
  if (query.sourceId) query.filter._source = query.sourceId;
  if (query.sourceType) query.filter._sourceType = query.sourceType;

  // Return only newer results
  if (query.since) {
    query.filter.storedAt = query.filter.storedAt || {};
    query.filter.storedAt.$gte = query.since;
  }

  // Re-set search timestamp
  query.since = new Date();

  if (!query.keywords) {
    // Just use filters when no keywords are provided
    Report.findPage(query.filter, page, function(err, reports) {
      if (err) return callback(err);
      callback(null, reports);
    });
  } else {
    Report.textSearch(query.keywords, _.pick(query, ['filter', 'limit']), function(err, reports) {
      if (err) return callback(err);
      var result = {
        total: reports.stats.n ? reports.stats.nscannedObjects : 0,
        results: _.pluck(reports.results, 'obj')
      };
      callback(null, result);
    });
  }
};

module.exports = Report;
<commit_msg>Sort search results by storedAt date, descending<commit_after>var database = require('../lib/database');
var mongoose = database.mongoose;
var textSearch = require('mongoose-text-search');
var Source = require('./source');
var Query = require('./query');
var _ = require('underscore');

var schema = new mongoose.Schema({
  authoredAt: Date,
  fetchedAt: Date,
  storedAt: Date,
  content: String,
  author: String,
  status: String,
  url: String,
  _source: {type: String, ref: 'Source'},
  _sourceType: String,
  _sourceNickname: String
});

// Give the report schema text search capabilities
schema.plugin(textSearch);
// Add a text index to the `content` field
schema.index({content: 'text'});

schema.pre('save', function(next) {
  if (this.isNew) {
    this._wasNew = true;
    this.storedAt = new Date();
  }
  next();
});

schema.post('save', function() {
  if (this._wasNew) schema.emit('report:save', {_id: this._id.toString()});
  else if (this.isModified('status')) schema.emit('report:status', {_id: this._id.toString(), status: this.status});
});

var Report = mongoose.model('Report', schema);

// Find reports using pagination
Report.findPage = function(filters, page, callback) {
  if (typeof filters === 'function') {
    callback = filters;
    filters = {};
    page = 0;
  }
  if (typeof page === 'function') {
    callback = page;
    page = 0;
  }
  if (page < 0) page = 0;
  var limit = 25;
  Report.count(filters, function(err, count) {
    if (err) return callback(err);
    var result = {total: count};
    Report.find(filters, null, {limit: limit, skip: page * limit, sort: '-storedAt'}, function(err, reports) {
      if (err) return callback(err);
      result.results = reports;
      callback(null, result);
    });
  });
};

// Query reports based on passed query data
Report.queryReports = function(query, page, callback) {
  if (typeof query === 'function') return Report.findPage(query);
  if (query instanceof Query) query = query.normalize();
  if (typeof page === 'function') {
    callback = page;
    page = 0;
  }
  if (page < 0) page = 0;

  query.limit = 100;
  query.filter = {};

  // Determine status filter
  if (query.status) {
    query.filter.status = {};
    if (query.status === 'assigned') query.filter.status.$exists = true;
    else if (query.status === 'unassigned') query.filter.status.$exists = false;
    else query.filter.status = query.status;
  }

  // Determine inclusive date filters
  if (query.after || query.before) {
    query.filter.storedAt = {};
    if (query.after) query.filter.storedAt.$gte = query.after;
    if (query.before) query.filter.storedAt.$lte = query.before;
  }

  // Convert sourceId and sourceType for Report compatibility
  if (query.sourceId) query.filter._source = query.sourceId;
  if (query.sourceType) query.filter._sourceType = query.sourceType;

  // Return only newer results
  if (query.since) {
    query.filter.storedAt = query.filter.storedAt || {};
    query.filter.storedAt.$gte = query.since;
  }

  // Re-set search timestamp
  query.since = new Date();

  if (!query.keywords) {
    // Just use filters when no keywords are provided
    Report.findPage(query.filter, page, function(err, reports) {
      if (err) return callback(err);
      callback(null, reports);
    });
  } else {
    Report.textSearch(query.keywords, _.pick(query, ['filter', 'limit']), function(err, reports) {
      if (err) return callback(err);
      var result = {
        total: reports.stats.n ? reports.stats.nscannedObjects : 0,
        results: _.chain(reports.results).pluck('obj').sortBy('storedAt').value().reverse()
      };
      callback(null, result);
    });
  }
};

module.exports = Report;
<|endoftext|>


<commit_before>/**
 * Created by ksinger on 30.03.2017.
 */

import angular from "angular";
import ngAnimate from "angular-animate";
import { actionCreators } from "../actions/actions.js";
import { attach, toAbsoluteURL, getIn, get } from "../utils.js";
import {
  connect2Redux,
  createDocumentDefinitionFromPost,
} from "../won-utils.js";
import * as needUtils from "../need-utils.js";
import * as processUtils from "../process-utils.js";
import * as generalSelectors from "../selectors/general-selectors.js";
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";

import { ownerBaseUrl } from "config";

import "style/_context-dropdown.scss";

const serviceDependencies = ["$scope", "$ngRedux", "$element"];
function genComponentConf() {
  let template = `
            <svg class="cdd__icon__small"
                ng-if="self.postLoading || self.postFailedToLoad"
                style="--local-primary:var(--won-skeleton-color);">
                    <use xlink:href="#ico16_contextmenu" href="#ico16_contextmenu"></use>
            </svg>
            <svg class="cdd__icon__small clickable"
                ng-if="!self.postLoading && !self.postFailedToLoad"
                style="--local-primary:var(--won-secondary-color);"
                ng-click="self.contextMenuOpen = true">
                    <use xlink:href="#ico16_contextmenu" href="#ico16_contextmenu"></use>
            </svg>

                        <svg class="cdd__icon__small__contextmenu clickable"
                            style="--local-primary:black;">
                            <use xlink:href="#ico16_contextmenu" href="#ico16_contextmenu"></use>
                        </svg>

                    <button class="won-button--outlined thin red"
                        ng-click="self.exportPdf()">
                        Export as PDF
                    </button>
                    <button
                        class="won-button--outlined thin red"
                        ng-if="self.isUsableAsTemplate"
                        ng-click="self.router__stateGoAbs('connections', {fromNeedUri: self.needUri, mode: 'DUPLICATE'})">
                        Post this too!
                    </button>
                    <button
                        class="won-button--outlined thin red"
                        ng-if="self.isEditable"
                        ng-click="self.router__stateGoAbs('connections', {fromNeedUri: self.needUri, mode: 'EDIT'})">
                        Edit
                    </button>

                        Report

                    <button class="won-button--filled red"
                        ng-if="self.isOwnPost && self.isInactive"
                        ng-click="self.reOpenPost()">
                        Reopen
                    </button>
                    <button class="won-button--filled red"
                        ng-if="self.isOwnPost && self.isInactive"
                        ng-click="self.deletePost()">
                        Delete
                    </button>
                    <button class="won-button--filled red"
                        ng-if="self.isOwnPost && self.isActive"
                        ng-click="self.closePost()">
                        Remove
                    </button>

        `;

  class Controller {
    constructor() {
      attach(this, serviceDependencies, arguments);

      const selectFromState = state => {
        const post = this.needUri && state.getIn(["needs", this.needUri]);

        let linkToPost;
        if (ownerBaseUrl && post) {
          const path = "#!post/" + `?postUri=${encodeURI(post.get("uri"))}`;

          linkToPost = toAbsoluteURL(ownerBaseUrl).toString() + path;
        }

        const process = get(state, "process");

        return {
          adminEmail: getIn(state, ["config", "theme", "adminEmail"]),
          isOwnPost: generalSelectors.isNeedOwned(state, this.needUri),
          isActive: needUtils.isActive(post),
          isInactive: needUtils.isInactive(post),
          isUsableAsTemplate: generalSelectors.isNeedUsableAsTemplate(
            state,
            this.needUri
          ),
          isEditable: generalSelectors.isNeedEditable(state, this.needUri),
          post,
          postLoading:
            !post || processUtils.isNeedLoading(process, post.get("uri")),
          postFailedToLoad:
            post && processUtils.hasNeedFailedToLoad(process, post.get("uri")),
          linkToPost,
        };
      };
      connect2Redux(selectFromState, actionCreators, ["self.needUri"], this);

      const callback = event => {
        const clickedElement = event.target;
        //hide MainMenu if click was outside of the component and menu was open
        if (
          this.contextMenuOpen &&
          !this.$element[0].contains(clickedElement)
        ) {
          this.contextMenuOpen = false;
          this.$scope.$apply();
        }
      };

      this.$scope.$on("$destroy", () => {
        window.document.removeEventListener("click", callback);
      });

      window.document.addEventListener("click", callback);
    }

    closePost() {
      if (this.isOwnPost) {
        const payload = {
          caption: "Attention!",
          text:
            "Deleting or archiving the Post will close all connections, do you want to proceed?",
          buttons: [
            {
              caption: "Delete",
              callback: () => {
                /*this.needs__delete(this.post.get("uri"));
                this.router__stateGoCurrent({
                  useCase: undefined,
                  postUri: undefined,
                });
                this.view__hideModalDialog();*/
                this.deletePost();
              },
            },
            {
              caption: "Archive",
              callback: () => {
                this.needs__close(this.post.get("uri"));
                this.router__stateGoCurrent({
                  useCase: undefined,
                  postUri: undefined,
                });
                this.view__hideModalDialog();
              },
            },
            {
              caption: "Cancel",
              callback: () => {
                this.view__hideModalDialog();
              },
            },
          ],
        };
        this.view__showModalDialog(payload);
      }
    }

    deletePost() {
      if (this.isOwnPost) {
        const payload = {
          caption: "Attention!",
          text: "Deleting the Post is irreversible, do you want to proceed?",
          buttons: [
            {
              caption: "YES",
              callback: () => {
                this.needs__delete(this.post.get("uri"));
                this.router__stateGoCurrent({
                  useCase: undefined,
                  postUri: undefined,
                });
                this.view__hideModalDialog();
              },
            },
            {
              caption: "NO",
              callback: () => {
                this.view__hideModalDialog();
              },
            },
          ],
        };
        this.view__showModalDialog(payload);
      }
    }

    generateReportPostMailParams() {
      const subject = `[Report Post] - ${this.needUri}`;
      const body = `Link to Post: ${this.linkToPost}%0D%0AReason:%0D%0A`; //hint: %0D%0A adds a linebreak

      return `subject=${subject}&body=${body}`;
    }

    reOpenPost() {
      if (this.isOwnPost) {
        this.needs__reopen(this.post.get("uri"));
      }
    }

    exportPdf() {
      if (!this.post) return;
      const docDefinition = createDocumentDefinitionFromPost(this.post);

      if (docDefinition) {
        pdfMake.vfs = pdfFonts.pdfMake.vfs;
        pdfMake.createPdf(docDefinition).download();
      }
    }
  }
  Controller.$inject = serviceDependencies;

  return {
    restrict: "E",
    controller: Controller,
    controllerAs: "self",
    bindToController: true, //scope-bindings -> ctrl
    scope: {
      needUri: "=",
    },
    template: template,
  };
}

export default angular
  .module("won.owner.components.postContextDropdown", [ngAnimate])
  .directive("wonPostContextDropdown", genComponentConf).name;
<commit_msg>Decaps YES NO modalDialog buttons<commit_after>/**
 * Created by ksinger on 30.03.2017.
 */

import angular from "angular";
import ngAnimate from "angular-animate";
import { actionCreators } from "../actions/actions.js";
import { attach, toAbsoluteURL, getIn, get } from "../utils.js";
import {
  connect2Redux,
  createDocumentDefinitionFromPost,
} from "../won-utils.js";
import * as needUtils from "../need-utils.js";
import * as processUtils from "../process-utils.js";
import * as generalSelectors from "../selectors/general-selectors.js";
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";

import { ownerBaseUrl } from "config";

import "style/_context-dropdown.scss";

const serviceDependencies = ["$scope", "$ngRedux", "$element"];
function genComponentConf() {
  let template = `
            <svg class="cdd__icon__small"
                ng-if="self.postLoading || self.postFailedToLoad"
                style="--local-primary:var(--won-skeleton-color);">
                    <use xlink:href="#ico16_contextmenu" href="#ico16_contextmenu"></use>
            </svg>
            <svg class="cdd__icon__small clickable"
                ng-if="!self.postLoading && !self.postFailedToLoad"
                style="--local-primary:var(--won-secondary-color);"
                ng-click="self.contextMenuOpen = true">
                    <use xlink:href="#ico16_contextmenu" href="#ico16_contextmenu"></use>
            </svg>

                        <svg class="cdd__icon__small__contextmenu clickable"
                            style="--local-primary:black;">
                            <use xlink:href="#ico16_contextmenu" href="#ico16_contextmenu"></use>
                        </svg>

                    <button class="won-button--outlined thin red"
                        ng-click="self.exportPdf()">
                        Export as PDF
                    </button>
                    <button
                        class="won-button--outlined thin red"
                        ng-if="self.isUsableAsTemplate"
                        ng-click="self.router__stateGoAbs('connections', {fromNeedUri: self.needUri, mode: 'DUPLICATE'})">
                        Post this too!
                    </button>
                    <button
                        class="won-button--outlined thin red"
                        ng-if="self.isEditable"
                        ng-click="self.router__stateGoAbs('connections', {fromNeedUri: self.needUri, mode: 'EDIT'})">
                        Edit
                    </button>

                        Report

                    <button class="won-button--filled red"
                        ng-if="self.isOwnPost && self.isInactive"
                        ng-click="self.reOpenPost()">
                        Reopen
                    </button>
                    <button class="won-button--filled red"
                        ng-if="self.isOwnPost && self.isInactive"
                        ng-click="self.deletePost()">
                        Delete
                    </button>
                    <button class="won-button--filled red"
                        ng-if="self.isOwnPost && self.isActive"
                        ng-click="self.closePost()">
                        Remove
                    </button>

        `;

  class Controller {
    constructor() {
      attach(this, serviceDependencies, arguments);

      const selectFromState = state => {
        const post = this.needUri && state.getIn(["needs", this.needUri]);

        let linkToPost;
        if (ownerBaseUrl && post) {
          const path = "#!post/" + `?postUri=${encodeURI(post.get("uri"))}`;

          linkToPost = toAbsoluteURL(ownerBaseUrl).toString() + path;
        }

        const process = get(state, "process");

        return {
          adminEmail: getIn(state, ["config", "theme", "adminEmail"]),
          isOwnPost: generalSelectors.isNeedOwned(state, this.needUri),
          isActive: needUtils.isActive(post),
          isInactive: needUtils.isInactive(post),
          isUsableAsTemplate: generalSelectors.isNeedUsableAsTemplate(
            state,
            this.needUri
          ),
          isEditable: generalSelectors.isNeedEditable(state, this.needUri),
          post,
          postLoading:
            !post || processUtils.isNeedLoading(process, post.get("uri")),
          postFailedToLoad:
            post && processUtils.hasNeedFailedToLoad(process, post.get("uri")),
          linkToPost,
        };
      };
      connect2Redux(selectFromState, actionCreators, ["self.needUri"], this);

      const callback = event => {
        const clickedElement = event.target;
        //hide MainMenu if click was outside of the component and menu was open
        if (
          this.contextMenuOpen &&
          !this.$element[0].contains(clickedElement)
        ) {
          this.contextMenuOpen = false;
          this.$scope.$apply();
        }
      };

      this.$scope.$on("$destroy", () => {
        window.document.removeEventListener("click", callback);
      });

      window.document.addEventListener("click", callback);
    }

    closePost() {
      if (this.isOwnPost) {
        const payload = {
          caption: "Attention!",
          text:
            "Deleting or archiving the Post will close all connections, do you want to proceed?",
          buttons: [
            {
              caption: "Delete",
              callback: () => {
                /*this.needs__delete(this.post.get("uri"));
                this.router__stateGoCurrent({
                  useCase: undefined,
                  postUri: undefined,
                });
                this.view__hideModalDialog();*/
                this.deletePost();
              },
            },
            {
              caption: "Archive",
              callback: () => {
                this.needs__close(this.post.get("uri"));
                this.router__stateGoCurrent({
                  useCase: undefined,
                  postUri: undefined,
                });
                this.view__hideModalDialog();
              },
            },
            {
              caption: "Cancel",
              callback: () => {
                this.view__hideModalDialog();
              },
            },
          ],
        };
        this.view__showModalDialog(payload);
      }
    }

    deletePost() {
      if (this.isOwnPost) {
        const payload = {
          caption: "Attention!",
          text: "Deleting the Post is irreversible, do you want to proceed?",
          buttons: [
            {
              caption: "Yes",
              callback: () => {
                this.needs__delete(this.post.get("uri"));
                this.router__stateGoCurrent({
                  useCase: undefined,
                  postUri: undefined,
                });
                this.view__hideModalDialog();
              },
            },
            {
              caption: "No",
              callback: () => {
                this.view__hideModalDialog();
              },
            },
          ],
        };
        this.view__showModalDialog(payload);
      }
    }

    generateReportPostMailParams() {
      const subject = `[Report Post] - ${this.needUri}`;
      const body = `Link to Post: ${this.linkToPost}%0D%0AReason:%0D%0A`; //hint: %0D%0A adds a linebreak

      return `subject=${subject}&body=${body}`;
    }

    reOpenPost() {
      if (this.isOwnPost) {
        this.needs__reopen(this.post.get("uri"));
      }
    }

    exportPdf() {
      if (!this.post) return;
      const docDefinition = createDocumentDefinitionFromPost(this.post);

      if (docDefinition) {
        pdfMake.vfs = pdfFonts.pdfMake.vfs;
        pdfMake.createPdf(docDefinition).download();
      }
    }
  }
  Controller.$inject = serviceDependencies;

  return {
    restrict: "E",
    controller: Controller,
    controllerAs: "self",
    bindToController: true, //scope-bindings -> ctrl
    scope: {
      needUri: "=",
    },
    template: template,
  };
}

export default angular
  .module("won.owner.components.postContextDropdown", [ngAnimate])
  .directive("wonPostContextDropdown", genComponentConf).name;
<|endoftext|>


<commit_before>var fs = require('fs');
var sys = require('sys')
var exec = require('child_process').exec;

var adminCommands = function(dbot) {
    var dbot = dbot;

    var commands = {
        'join': function(data, params) {
            dbot.instance.join(params[1]);
            dbot.say(data.channel, 'Joined ' + params[1]);
        },

        'opme': function(data, params) {
           dbot.instance.send('MODE ' + params[1] + ' +o ', data.user);
        },

        'part': function(data, params) {
            dbot.instance.part(params[1]);
        },

        // Do a git pull and reload
        'greload': function(data, params) {
            var child;

            child = exec("cd ../ && git pull", function (error, stdout, stderr) {
                commands.reload(data, params);
            }.bind(this));
        },

        'reload': function(data, params) {
            dbot.db = JSON.parse(fs.readFileSync('db.json', 'utf-8'));
            dbot.reloadModules();
            dbot.say(data.channel, 'Reloaded that shit.');
        },

        'say': function(data, params) {
            if (params[1] === "@") {
                var c = data.channel;
            } else {
                var c = params[1];
            }
            var m = params.slice(2).join(' ');
            dbot.say(c, m);
        },

        'act': function(data, params) {
            if (params[1] === "@") {
                var c = data.channel;
            } else {
                var c = params[1];
            }
            var m = params.slice(2).join(' ');
            dbot.act(c, m);
        },

        'load': function(data, params) {
            dbot.moduleNames.push(params[1]);
            dbot.reloadModules();
            dbot.say(data.channel, 'Loaded new module: ' + params[1]);
        },

        'unload': function(data, params) {
            if(dbot.moduleNames.include(params[1])) {
                var cacheKey = require.resolve('../modules/' + params[1]);
                delete require.cache[cacheKey];

                var moduleIndex = dbot.moduleNames.indexOf(params[1]);
                dbot.moduleNames.splice(moduleIndex, 1);

                dbot.reloadModules();
                dbot.say(data.channel, 'Turned off module: ' + params[1]);
            } else {
                dbot.say(data.channel, 'Module ' + params[1] + ' isn\'t loaded... Idiot...');
            }
        },

        'ban': function(data, params) {
            if(dbot.db.bans.hasOwnProperty(params[2])) {
                dbot.db.bans[params[2]].push(params[1]);
            } else {
                dbot.db.bans[params[2]] = [ params[1] ];
            }
            dbot.say(data.channel, params[1] + ' banned from ' + params[2]);
        },

        'unban': function(data, params) {
            if(dbot.db.bans.hasOwnProperty(params[2]) && dbot.db.bans[params[2]].include(params[1])) {
                dbot.db.bans[params[2]].splice(dbot.db.bans[params[2]].indexOf(params[1]), 1);
                dbot.say(data.channel, params[1] + ' unbanned from ' + params[2]);
            } else {
                dbot.say(data.channel, 'It appears ' + params[1] + 'wasn\'t banned from that command, you fool.');
            }
        },

        'modehate': function(data, params) {
            dbot.db.modehate.push(params[1]);
            dbot.say(data.channel, 'Now modehating on ' + params[1]);
        },

        'unmodehate': function(data, params) {
            dbot.db.modehate.splice(dbot.db.modehate.indexOf(params[1]), 1);
            dbot.say(data.channel, 'No longer modehating on ' + params[1]);
        },

        'lock': function(data, params) {
            dbot.db.locks.push(params[1]);
            dbot.say(data.channel, 'Locked ' + params[1] + ' quotes.');
        }
    };

    return {
        // These commands are implemented as their own listener so it can easily
        // check whether the user is the admin, and so that the admin can't ban
        // themselves and then not be able to rectify it...
        'listener': function(data) {
            if(data.channel == dbot.name) data.channel = data.user;

            params = data.message.split(' ');
            if(commands.hasOwnProperty(params[0]) && dbot.admin.include(data.user)) {
                commands[params[0]](data, params);
                dbot.save();
            }
        },

        'onLoad': function() {
            return {
                '~resetadmin': function(data, params) {
                    dbot.admin = dbot.config.admin;
                }
            }
        },

        'on': 'PRIVMSG'
    };
};

exports.fetch = function(dbot) {
    return adminCommands(dbot);
};
<commit_msg>print output of greload<commit_after>var fs = require('fs');
var sys = require('sys')
var exec = require('child_process').exec;

var adminCommands = function(dbot) {
    var dbot = dbot;

    var commands = {
        'join': function(data, params) {
            dbot.instance.join(params[1]);
            dbot.say(data.channel, 'Joined ' + params[1]);
        },

        'opme': function(data, params) {
           dbot.instance.send('MODE ' + params[1] + ' +o ', data.user);
        },

        'part': function(data, params) {
            dbot.instance.part(params[1]);
        },

        // Do a git pull and reload
        'greload': function(data, params) {
            var child;

            child = exec("cd ../ && git pull", function (error, stdout, stderr) {
                console.log(stdout);
                commands.reload(data, params);
            }.bind(this));
        },

        'reload': function(data, params) {
            dbot.db = JSON.parse(fs.readFileSync('db.json', 'utf-8'));
            dbot.reloadModules();
            dbot.say(data.channel, 'Reloaded that shit.');
        },

        'say': function(data, params) {
            if (params[1] === "@") {
                var c = data.channel;
            } else {
                var c = params[1];
            }
            var m = params.slice(2).join(' ');
            dbot.say(c, m);
        },

        'act': function(data, params) {
            if (params[1] === "@") {
                var c = data.channel;
            } else {
                var c = params[1];
            }
            var m = params.slice(2).join(' ');
            dbot.act(c, m);
        },

        'load': function(data, params) {
            dbot.moduleNames.push(params[1]);
            dbot.reloadModules();
            dbot.say(data.channel, 'Loaded new module: ' + params[1]);
        },

        'unload': function(data, params) {
            if(dbot.moduleNames.include(params[1])) {
                var cacheKey = require.resolve('../modules/' + params[1]);
                delete require.cache[cacheKey];

                var moduleIndex = dbot.moduleNames.indexOf(params[1]);
                dbot.moduleNames.splice(moduleIndex, 1);

                dbot.reloadModules();
                dbot.say(data.channel, 'Turned off module: ' + params[1]);
            } else {
                dbot.say(data.channel, 'Module ' + params[1] + ' isn\'t loaded... Idiot...');
            }
        },

        'ban': function(data, params) {
            if(dbot.db.bans.hasOwnProperty(params[2])) {
                dbot.db.bans[params[2]].push(params[1]);
            } else {
                dbot.db.bans[params[2]] = [ params[1] ];
            }
            dbot.say(data.channel, params[1] + ' banned from ' + params[2]);
        },

        'unban': function(data, params) {
            if(dbot.db.bans.hasOwnProperty(params[2]) && dbot.db.bans[params[2]].include(params[1])) {
                dbot.db.bans[params[2]].splice(dbot.db.bans[params[2]].indexOf(params[1]), 1);
                dbot.say(data.channel, params[1] + ' unbanned from ' + params[2]);
            } else {
                dbot.say(data.channel, 'It appears ' + params[1] + 'wasn\'t banned from that command, you fool.');
            }
        },

        'modehate': function(data, params) {
            dbot.db.modehate.push(params[1]);
            dbot.say(data.channel, 'Now modehating on ' + params[1]);
        },

        'unmodehate': function(data, params) {
            dbot.db.modehate.splice(dbot.db.modehate.indexOf(params[1]), 1);
            dbot.say(data.channel, 'No longer modehating on ' + params[1]);
        },

        'lock': function(data, params) {
            dbot.db.locks.push(params[1]);
            dbot.say(data.channel, 'Locked ' + params[1] + ' quotes.');
        }
    };

    return {
        // These commands are implemented as their own listener so it can easily
        // check whether the user is the admin, and so that the admin can't ban
        // themselves and then not be able to rectify it...
        'listener': function(data) {
            if(data.channel == dbot.name) data.channel = data.user;

            params = data.message.split(' ');
            if(commands.hasOwnProperty(params[0]) && dbot.admin.include(data.user)) {
                commands[params[0]](data, params);
                dbot.save();
            }
        },

        'onLoad': function() {
            return {
                '~resetadmin': function(data, params) {
                    dbot.admin = dbot.config.admin;
                }
            }
        },

        'on': 'PRIVMSG'
    };
};

exports.fetch = function(dbot) {
    return adminCommands(dbot);
};
<|endoftext|>


<commit_before>import Immutable from "immutable";
import vocab from "../../service/vocab.js";
import * as useCaseUtils from "../../usecase-utils.js";
import * as atomUtils from "../../redux/utils/atom-utils.js";
import {
  generateHexColor,
  generateRgbColorArray,
  generateFakePersonaName,
  get,
  getIn,
} from "../../utils.js";
import shajs from "sha.js";
import Identicon from "identicon.js";

export function parseAtom(jsonldAtom) {
  const jsonldAtomImm = Immutable.fromJS(jsonldAtom);

  const atomUri = get(jsonldAtomImm, "@id");
  if (atomUri) {
    const detailsToParse = useCaseUtils.getAllDetails();

    let parsedAtom = {
      uri: atomUri,
      identiconSvg: generateIdenticon(atomUri),
      nodeUri: getIn(jsonldAtomImm, [vocab.WON.wonNodeCompacted, "@id"]),
      state: extractState(jsonldAtomImm),
      content: generateContent(jsonldAtomImm, detailsToParse),
      seeks: generateContent(get(jsonldAtomImm, "match:seeks"), detailsToParse),
      creationDate: extractCreationDate(jsonldAtomImm),
      lastUpdateDate: extractCreationDate(jsonldAtomImm), //Used for sorting/updates (e.g. if connection comes in etc...)
      modifiedDate: extractLastModifiedDate(jsonldAtomImm), //Used as a flag if the atom itself has changed (e.g. atom edit)
      humanReadable: undefined, //can only be determined after we generated The Content
      fakePersonaName: generateFakePersonaName(atomUri),
      matchedUseCase: {
        identifier: undefined,
        icon: undefined,
        reactions: undefined,
      },
      background: generateBackground(atomUri),
      unread: false,
      isBeingCreated: false,
      connections: Immutable.Map(),
    };

    if (!parsedAtom.creationDate || !parsedAtom.lastUpdateDate) {
      console.error(
        "Cant parse atom, creationDate or lastUpdateDate not set",
        jsonldAtomImm && jsonldAtomImm.toJS()
      );
      return undefined;
    }

    let parsedAtomImm = Immutable.fromJS(parsedAtom);
    const matchingUseCase = useCaseUtils.findUseCaseByAtom(parsedAtomImm);

    if (matchingUseCase) {
      parsedAtomImm = parsedAtomImm
        .setIn(["matchedUseCase", "identifier"], matchingUseCase.identifier)
        .setIn(["matchedUseCase", "icon"], matchingUseCase.icon)
        .setIn(
          ["matchedUseCase", "reactions"],
          matchingUseCase.reactions
            ? Immutable.fromJS(matchingUseCase.reactions)
            : Immutable.Map()
        );
    }

    parsedAtomImm = parsedAtomImm.set(
      "humanReadable",
      getHumanReadableStringFromAtom(parsedAtomImm, detailsToParse)
    );

    return parsedAtomImm;
  } else {
    console.error(
      "Cant parse atom, data is an invalid atom-object: ",
      jsonldAtomImm && jsonldAtomImm.toJS()
    );
    return undefined;
  }
}

/**
 * Tries to extract all the metaData of a given metaAtom into a state that is similar to the ones we use for the parseAtom
 * in general, metaAtoms are retrieved when accessing whatsNew and whatsAround (for now) and are json structures based on the
 * AtomPojo of the owner app.
 * @param metaAtom
 * @returns {*}
 */
export function parseMetaAtom(metaAtom) {
  const metaAtomImm = Immutable.fromJS(metaAtom);

  const extractEventObjectAboutUris = eventObjectUris =>
    eventObjectUris &&
    Immutable.Set(
      eventObjectUris.map(eventObjectUri =>
        eventObjectUri
          .replace(vocab.WON.baseUri, vocab.WON.prefix + ":")
          .replace(vocab.WONCON.baseUri, vocab.WONCON.prefix + ":")
          .replace(vocab.DEMO.baseUri, vocab.DEMO.prefix + ":")
          .replace(vocab.WONMATCH.baseUri, vocab.WONMATCH.prefix + ":")
          .replace("http://schema.org/", "s:")
      )
    );

  const extractTypes = types =>
    types &&
    Immutable.Set(
      types.map(type =>
        type
          .replace(vocab.WON.baseUri, vocab.WON.prefix + ":")
          .replace(vocab.WONCON.baseUri, vocab.WONCON.prefix + ":")
          .replace(vocab.DEMO.baseUri, vocab.DEMO.prefix + ":")
          .replace(vocab.BOT.baseUri, vocab.BOT.prefix + ":")
          .replace(vocab.WONMATCH.baseUri, vocab.WONMATCH.prefix + ":")
          .replace("http://schema.org/", "s:")
      )
    );
  const extractFlags = flags =>
    flags &&
    flags.map(flag =>
      flag
        .replace(vocab.WON.baseUri, vocab.WON.prefix + ":")
        .replace(vocab.WONCON.baseUri, vocab.WONCON.prefix + ":")
        .replace(vocab.DEMO.baseUri, vocab.DEMO.prefix + ":")
        .replace(vocab.WONMATCH.baseUri, vocab.WONMATCH.prefix + ":")
        .replace("http://schema.org/", "s:")
    );
  const extractLocation = location =>
    location && {
      address: "",
      lat: get(location, "latitude"),
      lng: get(location, "longitude"),
    };

  const extractSockets = socketTypeUriMap =>
    socketTypeUriMap &&
    socketTypeUriMap.map(socketType =>
      socketType
        .replace(vocab.CHAT.baseUri, vocab.CHAT.prefix + ":")
        .replace(vocab.GROUP.baseUri, vocab.GROUP.prefix + ":")
        .replace(vocab.HOLD.baseUri, vocab.HOLD.prefix + ":")
        .replace(vocab.BUDDY.baseUri, vocab.BUDDY.prefix + ":")
        .replace(vocab.BOT.baseUri, vocab.BOT.prefix + ":")
        .replace(vocab.WXSCHEMA.baseUri, vocab.WXSCHEMA.prefix + ":")
    );

  const extractStateFromMeta = state => {
    switch (state) {
      case "ACTIVE":
        return vocab.WON.ActiveCompacted;
      case "INACTIVE":
        return vocab.WON.InactiveCompacted;
      case "DELETED":
        return vocab.WON.DeletedCompacted;
      default:
        return undefined;
    }
  };

  if (metaAtomImm) {
    let parsedMetaAtom = {
      uri: get(metaAtomImm, "uri"),
      identiconSvg: undefined,
      nodeUri: undefined,
      state: extractStateFromMeta(get(metaAtomImm, "state")),
      rating: undefined,
      content: {
        type: extractTypes(get(metaAtomImm, "types")),
        sockets: extractSockets(get(metaAtomImm, "socketTypeUriMap")),
        flags: extractFlags(get(metaAtomImm, "flags")),
        location: extractLocation(get(metaAtomImm, "location")),
        jobLocation: extractLocation(get(metaAtomImm, "jobLocation")),
        eventObjectAboutUris: extractEventObjectAboutUris(
          get(metaAtomImm, "eventObjectAboutUris")
        ),
      },
      seeks: {
        type: extractTypes(get(metaAtomImm, "seeksTypes")),
        eventObjectAboutUris: extractEventObjectAboutUris(
          get(metaAtomImm, "seeksEventObjectAboutUris")
        ),
      },
      modifiedDate:
        get(metaAtomImm, "modifiedDate") &&
        new Date(get(metaAtomImm, "modifiedDate")),
      creationDate:
        get(metaAtomImm, "creationDate") &&
        new Date(get(metaAtomImm, "creationDate")),
      lastUpdateDate:
        get(metaAtomImm, "creationDate") &&
        new Date(get(metaAtomImm, "creationDate")),
      humanReadable: undefined,
      fakePersonaName: generateFakePersonaName(get(metaAtomImm, "uri")),
      matchedUseCase: {
        identifier: undefined,
        icon: undefined,
        reactions: undefined,
      },
      background: generateBackground(get(metaAtomImm, "uri")),
      unread: false,
      isBeingCreated: false,
      connections: Immutable.Map(),
    };

    if (
      parsedMetaAtom.state &&
      parsedMetaAtom.modifiedDate &&
      parsedMetaAtom.creationDate
    ) {
      const parsedAtomImm = Immutable.fromJS(parsedMetaAtom);
      const matchingUseCase = useCaseUtils.findUseCaseByAtom(parsedAtomImm);

      if (matchingUseCase) {
        return parsedAtomImm
          .setIn(["matchedUseCase", "identifier"], matchingUseCase.identifier)
          .setIn(["matchedUseCase", "icon"], matchingUseCase.icon)
          .setIn(
            ["matchedUseCase", "reactions"],
            matchingUseCase.reactions
              ? Immutable.fromJS(matchingUseCase.reactions)
              : Immutable.Map()
          );
      }

      return parsedAtomImm;
    } else {
      console.error(
        "Cant parse metaAtom, data is an invalid atom-object: ",
        metaAtomImm && metaAtomImm.toJS()
      );
      return undefined;
    }
  }
  return undefined;
}

window.parseMetaAtom4dbg = parseMetaAtom;

/**
 * Tries to extract all the detailsToParse from the given contentJsonLd
 * uses the parseFromRdf function defined in the detail to extract the content
 * uses the detail identifier as the key of the contentDetail that is to be added
 * @param contentJsonLd
 * @param detailsToParse
 * @returns {title: *, tags: *}
 */
function generateContent(contentJsonLd, detailsToParse) {
  let content = {};
  if (contentJsonLd && contentJsonLd.size > 0 && detailsToParse) {
    for (const detailKey in detailsToParse) {
      const detailToParse = detailsToParse[detailKey];
      const detailIdentifier = detailToParse && detailToParse.identifier;
      const detailValue =
        detailToParse && detailToParse.parseFromRDF(contentJsonLd);

      if (detailIdentifier && detailValue) {
        content[detailIdentifier] = detailValue;
      }
    }
  }

  return content;
}

function extractState(atomJsonLd) {
  // we use to check for active
  // state and everything else
  // will be inactive
  return getIn(atomJsonLd, [vocab.WON.atomStateCompacted, "@id"]) ===
    vocab.WON.ActiveCompacted
    ? vocab.WON.ActiveCompacted
    : vocab.WON.InactiveCompacted;
}

function generateIdenticon(atomUri) {
  if (!atomUri) {
    return;
  }
  // quick extra hash here as identicon.js only uses first 15
  // chars (which aren't very unique for our uris due to the base-url):
  const hash = new shajs.sha512().update(atomUri).digest("hex");
  const rgbColorArray = generateRgbColorArray(hash);
  const idc = new Identicon(hash, {
    size: 100,
    foreground: [255, 255, 255, 255], // rgba white
    background: [...rgbColorArray, 255], // rgba
    margin: 0.2,
    format: "svg",
  });
  return idc.toString();
}

function generateBackground(atomUri) {
  if (!atomUri) {
    return;
  }

  const hash = new shajs.sha512().update(atomUri).digest("hex");
  return generateHexColor(hash);
}

function extractCreationDate(atomJsonLd) {
  const creationDate =
    get(atomJsonLd, "dct:created") ||
    get(atomJsonLd, "http://purl.org/dc/terms/created");
  if (creationDate) {
    return new Date(get(creationDate, "@value"));
  }
  return undefined;
}

function extractLastModifiedDate(atomJsonLd) {
  const lastModifiedDate =
    get(atomJsonLd, "dct:modified") ||
    get(atomJsonLd, "http://purl.org/dc/terms/modified");
  if (lastModifiedDate) {
    return new Date(get(lastModifiedDate, "@value"));
  }
  return undefined;
}

function getHumanReadableStringFromAtom(atomImm, detailsToParse) {
  if (atomImm && detailsToParse) {
    const atomContent = get(atomImm, "content");
    const seeksBranch = get(atomImm, "seeks");

    const title = get(atomContent, "title");
    const seeksTitle = get(seeksBranch, "title");

    if (atomUtils.isServiceAtom(atomImm) || atomUtils.isPersona(atomImm)) {
      return getIn(atomImm, ["content", "personaName"]);
    }

    if (getIn(atomImm, ["matchedUseCase", "identifier"]) === "pokemonGoRaid") {
      let raidReadable;
      let locationReadable;
      let gymReadable;

      const raidDetail = "pokemonRaid";
      const raidValueImm = getIn(atomImm, ["content", raidDetail]);
      if (raidValueImm && detailsToParse[raidDetail]) {
        raidReadable = detailsToParse[raidDetail].generateHumanReadable({
          value: raidValueImm.toJS(),
          includeLabel: false,
        });
      }

      const locationDetail = "location";
      const locationValueImm = getIn(atomImm, ["content", locationDetail]);
      if (locationValueImm && detailsToParse[locationDetail]) {
        locationReadable = detailsToParse[locationDetail].generateHumanReadable(
          {
            value: locationValueImm.toJS(),
            includeLabel: false,
          }
        );
      }

      const gymDetail = "pokemonGymInfo";
      const gymValueImm = getIn(atomImm, ["content", gymDetail]);
      if (gymValueImm && detailsToParse[gymDetail]) {
        gymReadable = detailsToParse[gymDetail].generateHumanReadable({
          value: gymValueImm.toJS(),
          includeLabel: false,
        });
      }
      if (raidReadable && locationReadable) {
        return (
          raidReadable +
          " @ " +
          locationReadable +
          (gymReadable ? " (" + gymReadable + ")" : "")
        );
      }
    }

    if (title && seeksTitle) {
      return title + " - " + seeksTitle;
    } else if (seeksTitle) {
      return seeksTitle;
    } else if (title) {
      return title;
    }

    let humanReadableDetails = generateHumanReadableArray(
      atomContent.toJS(),
      detailsToParse
    );
    let humanReadableSeeksDetails = generateHumanReadableArray(
      seeksBranch.toJS(),
      detailsToParse
    );

    if (
      humanReadableDetails.length > 0 &&
      humanReadableSeeksDetails.length > 0
    ) {
      return (
        humanReadableDetails.join(" ") +
        " Seeks: " +
        humanReadableSeeksDetails.join(", ")
      );
    } else if (humanReadableDetails.length > 0) {
      return humanReadableDetails.join(", ");
    } else if (humanReadableSeeksDetails.length > 0) {
      return humanReadableSeeksDetails.join(", ");
    }
  }
  return undefined;
}

function generateHumanReadableArray(presentDetails, detailsToParse) {
  let humanReadableArray = [];
  if (presentDetails && detailsToParse) {
    for (const key in presentDetails) {
      if (!(key === "sockets" || key === "type")) {
        const detailToParse = detailsToParse[key];
        if (detailToParse) {
          const detailValue = presentDetails[key];
          const detailValueJS =
            detailValue && Immutable.Iterable.isIterable(detailValue)
              ? detailValue.toJS()
              : detailValue;

          if (detailValueJS) {
            humanReadableArray.push(
              detailToParse.generateHumanReadable({
                value: detailValueJS,
                includeLabel: true,
              })
            );
          }
        }
      }
    }
  }
  return humanReadableArray;
}
<commit_msg>Add metaAtom parsing for resources<commit_after>import Immutable from "immutable";
import vocab from "../../service/vocab.js";
import * as useCaseUtils from "../../usecase-utils.js";
import * as atomUtils from "../../redux/utils/atom-utils.js";
import {
  generateHexColor,
  generateRgbColorArray,
  generateFakePersonaName,
  get,
  getIn,
} from "../../utils.js";
import shajs from "sha.js";
import Identicon from "identicon.js";

export function parseAtom(jsonldAtom) {
  const jsonldAtomImm = Immutable.fromJS(jsonldAtom);

  const atomUri = get(jsonldAtomImm, "@id");
  if (atomUri) {
    const detailsToParse = useCaseUtils.getAllDetails();

    let parsedAtom = {
      uri: atomUri,
      identiconSvg: generateIdenticon(atomUri),
      nodeUri: getIn(jsonldAtomImm, [vocab.WON.wonNodeCompacted, "@id"]),
      state: extractState(jsonldAtomImm),
      content: generateContent(jsonldAtomImm, detailsToParse),
      seeks: generateContent(get(jsonldAtomImm, "match:seeks"), detailsToParse),
      creationDate: extractCreationDate(jsonldAtomImm),
      lastUpdateDate: extractCreationDate(jsonldAtomImm), //Used for sorting/updates (e.g. if connection comes in etc...)
      modifiedDate: extractLastModifiedDate(jsonldAtomImm), //Used as a flag if the atom itself has changed (e.g. atom edit)
      humanReadable: undefined, //can only be determined after we generated The Content
      fakePersonaName: generateFakePersonaName(atomUri),
      matchedUseCase: {
        identifier: undefined,
        icon: undefined,
        reactions: undefined,
      },
      background: generateBackground(atomUri),
      unread: false,
      isBeingCreated: false,
      connections: Immutable.Map(),
    };

    if (!parsedAtom.creationDate || !parsedAtom.lastUpdateDate) {
      console.error(
        "Cant parse atom, creationDate or lastUpdateDate not set",
        jsonldAtomImm && jsonldAtomImm.toJS()
      );
      return undefined;
    }

    let parsedAtomImm = Immutable.fromJS(parsedAtom);
    const matchingUseCase = useCaseUtils.findUseCaseByAtom(parsedAtomImm);

    if (matchingUseCase) {
      parsedAtomImm = parsedAtomImm
        .setIn(["matchedUseCase", "identifier"], matchingUseCase.identifier)
        .setIn(["matchedUseCase", "icon"], matchingUseCase.icon)
        .setIn(
          ["matchedUseCase", "reactions"],
          matchingUseCase.reactions
            ? Immutable.fromJS(matchingUseCase.reactions)
            : Immutable.Map()
        );
    }

    parsedAtomImm = parsedAtomImm.set(
      "humanReadable",
      getHumanReadableStringFromAtom(parsedAtomImm, detailsToParse)
    );

    return parsedAtomImm;
  } else {
    console.error(
      "Cant parse atom, data is an invalid atom-object: ",
      jsonldAtomImm && jsonldAtomImm.toJS()
    );
    return undefined;
  }
}

/**
 * Tries to extract all the metaData of a given metaAtom into a state that is similar to the ones we use for the parseAtom
 * in general, metaAtoms are retrieved when accessing whatsNew and whatsAround (for now) and are json structures based on the
 * AtomPojo of the owner app.
 * @param metaAtom
 * @returns {*}
 */
export function parseMetaAtom(metaAtom) {
  const metaAtomImm = Immutable.fromJS(metaAtom);

  const extractEventObjectAboutUris = eventObjectUris =>
    eventObjectUris &&
    Immutable.Set(
      eventObjectUris.map(eventObjectUri =>
        eventObjectUri
          .replace(vocab.WON.baseUri, vocab.WON.prefix + ":")
          .replace(vocab.WONCON.baseUri, vocab.WONCON.prefix + ":")
          .replace(vocab.DEMO.baseUri, vocab.DEMO.prefix + ":")
          .replace(vocab.WONMATCH.baseUri, vocab.WONMATCH.prefix + ":")
          .replace("http://schema.org/", "s:")
      )
    );

  const extractTypes = types =>
    types &&
    Immutable.Set(
      types.map(type =>
        type
          .replace(vocab.WON.baseUri, vocab.WON.prefix + ":")
          .replace(vocab.WONCON.baseUri, vocab.WONCON.prefix + ":")
          .replace(vocab.DEMO.baseUri, vocab.DEMO.prefix + ":")
          .replace(vocab.BOT.baseUri, vocab.BOT.prefix + ":")
          .replace(vocab.WONMATCH.baseUri, vocab.WONMATCH.prefix + ":")
          .replace(vocab.VALUEFLOWS.baseUri, vocab.VALUEFLOWS.prefix + ":")
          .replace(vocab.WXVALUEFLOWS.baseUri, vocab.WXVALUEFLOWS.prefix + ":")
          .replace("http://schema.org/", "s:")
      )
    );
  const extractFlags = flags =>
    flags &&
    flags.map(flag =>
      flag
        .replace(vocab.WON.baseUri, vocab.WON.prefix + ":")
        .replace(vocab.WONCON.baseUri, vocab.WONCON.prefix + ":")
        .replace(vocab.DEMO.baseUri, vocab.DEMO.prefix + ":")
        .replace(vocab.WONMATCH.baseUri, vocab.WONMATCH.prefix + ":")
        .replace("http://schema.org/", "s:")
    );
  const extractLocation = location =>
    location && {
      address: "",
      lat: get(location, "latitude"),
      lng: get(location, "longitude"),
    };

  const extractSockets = socketTypeUriMap =>
    socketTypeUriMap &&
    socketTypeUriMap.map(socketType =>
      socketType
        .replace(vocab.CHAT.baseUri, vocab.CHAT.prefix + ":")
        .replace(vocab.GROUP.baseUri, vocab.GROUP.prefix + ":")
        .replace(vocab.HOLD.baseUri, vocab.HOLD.prefix + ":")
        .replace(vocab.BUDDY.baseUri, vocab.BUDDY.prefix + ":")
        .replace(vocab.BOT.baseUri, vocab.BOT.prefix + ":")
        .replace(vocab.WXSCHEMA.baseUri, vocab.WXSCHEMA.prefix + ":")
        .replace(vocab.WXVALUEFLOWS.baseUri, vocab.WXVALUEFLOWS.prefix + ":")
    );

  const extractStateFromMeta = state => {
    switch (state) {
      case "ACTIVE":
        return vocab.WON.ActiveCompacted;
      case "INACTIVE":
        return vocab.WON.InactiveCompacted;
      case "DELETED":
        return vocab.WON.DeletedCompacted;
      default:
        return undefined;
    }
  };

  if (metaAtomImm) {
    let parsedMetaAtom = {
      uri: get(metaAtomImm, "uri"),
      identiconSvg: undefined,
      nodeUri: undefined,
      state: extractStateFromMeta(get(metaAtomImm, "state")),
      rating: undefined,
      content: {
        type: extractTypes(get(metaAtomImm, "types")),
        sockets: extractSockets(get(metaAtomImm, "socketTypeUriMap")),
        flags: extractFlags(get(metaAtomImm, "flags")),
        location: extractLocation(get(metaAtomImm, "location")),
        jobLocation: extractLocation(get(metaAtomImm, "jobLocation")),
        eventObjectAboutUris: extractEventObjectAboutUris(
          get(metaAtomImm, "eventObjectAboutUris")
        ),
      },
      seeks: {
        type: extractTypes(get(metaAtomImm, "seeksTypes")),
        eventObjectAboutUris: extractEventObjectAboutUris(
          get(metaAtomImm, "seeksEventObjectAboutUris")
        ),
      },
      modifiedDate:
        get(metaAtomImm, "modifiedDate") &&
        new Date(get(metaAtomImm, "modifiedDate")),
      creationDate:
        get(metaAtomImm, "creationDate") &&
        new Date(get(metaAtomImm, "creationDate")),
      lastUpdateDate:
        get(metaAtomImm, "creationDate") &&
        new Date(get(metaAtomImm, "creationDate")),
      humanReadable: undefined,
      fakePersonaName: generateFakePersonaName(get(metaAtomImm, "uri")),
      matchedUseCase: {
        identifier: undefined,
        icon: undefined,
        reactions: undefined,
      },
      background: generateBackground(get(metaAtomImm, "uri")),
      unread: false,
      isBeingCreated: false,
      connections: Immutable.Map(),
    };

    if (
      parsedMetaAtom.state &&
      parsedMetaAtom.modifiedDate &&
      parsedMetaAtom.creationDate
    ) {
      const parsedAtomImm = Immutable.fromJS(parsedMetaAtom);
      const matchingUseCase = useCaseUtils.findUseCaseByAtom(parsedAtomImm);

      if (matchingUseCase) {
        return parsedAtomImm
          .setIn(["matchedUseCase", "identifier"], matchingUseCase.identifier)
          .setIn(["matchedUseCase", "icon"], matchingUseCase.icon)
          .setIn(
            ["matchedUseCase", "reactions"],
            matchingUseCase.reactions
              ? Immutable.fromJS(matchingUseCase.reactions)
              : Immutable.Map()
          );
      }

      return parsedAtomImm;
    } else {
      console.error(
        "Cant parse metaAtom, data is an invalid atom-object: ",
        metaAtomImm && metaAtomImm.toJS()
      );
      return undefined;
    }
  }
  return undefined;
}

window.parseMetaAtom4dbg = parseMetaAtom;

/**
 * Tries to extract all the detailsToParse from the given contentJsonLd
 * uses the parseFromRdf function defined in the detail to extract the content
 * uses the detail identifier as the key of the contentDetail that is to be added
 * @param contentJsonLd
 * @param detailsToParse
 * @returns {title: *, tags: *}
 */
function generateContent(contentJsonLd, detailsToParse) {
  let content = {};
  if (contentJsonLd && contentJsonLd.size > 0 && detailsToParse) {
    for (const detailKey in detailsToParse) {
      const detailToParse = detailsToParse[detailKey];
      const detailIdentifier = detailToParse && detailToParse.identifier;
      const detailValue =
        detailToParse && detailToParse.parseFromRDF(contentJsonLd);

      if (detailIdentifier && detailValue) {
        content[detailIdentifier] = detailValue;
      }
    }
  }

  return content;
}

function extractState(atomJsonLd) {
  // we use to check for active
  // state and everything else
  // will be inactive
  return getIn(atomJsonLd, [vocab.WON.atomStateCompacted, "@id"]) ===
    vocab.WON.ActiveCompacted
    ? vocab.WON.ActiveCompacted
    : vocab.WON.InactiveCompacted;
}

function generateIdenticon(atomUri) {
  if (!atomUri) {
    return;
  }
  // quick extra hash here as identicon.js only uses first 15
  // chars (which aren't very unique for our uris due to the base-url):
  const hash = new shajs.sha512().update(atomUri).digest("hex");
  const rgbColorArray = generateRgbColorArray(hash);
  const idc = new Identicon(hash, {
    size: 100,
    foreground: [255, 255, 255, 255], // rgba white
    background: [...rgbColorArray, 255], // rgba
    margin: 0.2,
    format: "svg",
  });
  return idc.toString();
}

function generateBackground(atomUri) {
  if (!atomUri) {
    return;
  }

  const hash = new shajs.sha512().update(atomUri).digest("hex");
  return generateHexColor(hash);
}

function extractCreationDate(atomJsonLd) {
  const creationDate =
    get(atomJsonLd, "dct:created") ||
    get(atomJsonLd, "http://purl.org/dc/terms/created");
  if (creationDate) {
    return new Date(get(creationDate, "@value"));
  }
  return undefined;
}

function extractLastModifiedDate(atomJsonLd) {
  const lastModifiedDate =
    get(atomJsonLd, "dct:modified") ||
    get(atomJsonLd, "http://purl.org/dc/terms/modified");
  if (lastModifiedDate) {
    return new Date(get(lastModifiedDate, "@value"));
  }
  return undefined;
}

function getHumanReadableStringFromAtom(atomImm, detailsToParse) {
  if (atomImm && detailsToParse) {
    const atomContent = get(atomImm, "content");
    const seeksBranch = get(atomImm, "seeks");

    const title = get(atomContent, "title");
    const seeksTitle = get(seeksBranch, "title");

    if (atomUtils.isServiceAtom(atomImm) || atomUtils.isPersona(atomImm)) {
      return getIn(atomImm, ["content", "personaName"]);
    }

    if (getIn(atomImm, ["matchedUseCase", "identifier"]) === "pokemonGoRaid") {
      let raidReadable;
      let locationReadable;
      let gymReadable;

      const raidDetail = "pokemonRaid";
      const raidValueImm = getIn(atomImm, ["content", raidDetail]);
      if (raidValueImm && detailsToParse[raidDetail]) {
        raidReadable = detailsToParse[raidDetail].generateHumanReadable({
          value: raidValueImm.toJS(),
          includeLabel: false,
        });
      }

      const locationDetail = "location";
      const locationValueImm = getIn(atomImm, ["content", locationDetail]);
      if (locationValueImm && detailsToParse[locationDetail]) {
        locationReadable = detailsToParse[locationDetail].generateHumanReadable(
          {
            value: locationValueImm.toJS(),
            includeLabel: false,
          }
        );
      }

      const gymDetail = "pokemonGymInfo";
      const gymValueImm = getIn(atomImm, ["content", gymDetail]);
      if (gymValueImm && detailsToParse[gymDetail]) {
        gymReadable = detailsToParse[gymDetail].generateHumanReadable({
          value: gymValueImm.toJS(),
          includeLabel: false,
        });
      }
      if (raidReadable && locationReadable) {
        return (
          raidReadable +
          " @ " +
          locationReadable +
          (gymReadable ? " (" + gymReadable + ")" : "")
        );
      }
    }

    if (title && seeksTitle) {
      return title + " - " + seeksTitle;
    } else if (seeksTitle) {
      return seeksTitle;
    } else if (title) {
      return title;
    }

    let humanReadableDetails = generateHumanReadableArray(
      atomContent.toJS(),
      detailsToParse
    );
    let humanReadableSeeksDetails = generateHumanReadableArray(
      seeksBranch.toJS(),
      detailsToParse
    );

    if (
      humanReadableDetails.length > 0 &&
      humanReadableSeeksDetails.length > 0
    ) {
      return (
        humanReadableDetails.join(" ") +
        " Seeks: " +
        humanReadableSeeksDetails.join(", ")
      );
    } else if (humanReadableDetails.length > 0) {
      return humanReadableDetails.join(", ");
    } else if (humanReadableSeeksDetails.length > 0) {
      return humanReadableSeeksDetails.join(", ");
    }
  }
  return undefined;
}

function generateHumanReadableArray(presentDetails, detailsToParse) {
  let humanReadableArray = [];
  if (presentDetails && detailsToParse) {
    for (const key in presentDetails) {
      if (!(key === "sockets" || key === "type")) {
        const detailToParse = detailsToParse[key];
        if (detailToParse) {
          const detailValue = presentDetails[key];
          const detailValueJS =
            detailValue && Immutable.Iterable.isIterable(detailValue)
              ? detailValue.toJS()
              : detailValue;

          if (detailValueJS) {
            humanReadableArray.push(
              detailToParse.generateHumanReadable({
                value: detailValueJS,
                includeLabel: true,
              })
            );
          }
        }
      }
    }
  }
  return humanReadableArray;
}
<|endoftext|>


<commit_before>"use strict";

const utils = require('./../../../utils'),

marked = require('marked'),
hljs = require('highlight.js');

marked.setOptions({
  sanitize: false,
  highlight: function(code) {
    return hljs.highlightAuto(code).value;
  }
});

// yamb.prototype.html()

module.exports = function() {
  return function html(text) {
    if (!utils.is.str(text)) {
      return false;
    }

    return marked(text);
  };
};<commit_msg>Check if code language is define<commit_after>"use strict";

const utils = require('./../../../utils'),

marked = require('marked'),
hljs = require('highlight.js');

marked.setOptions({
  sanitize: false,
  highlight: function(code, lang) {
    if (lang && hljs.LANGUAGES.hasOwnProperty(lang)) {
      return hljs.highlight(lang, code).value;
    } else {
      return code;
    }
  }
});

// yamb.prototype.html()

module.exports = function() {
  return function html(text) {
    if (!utils.is.str(text)) {
      return false;
    }

    return marked(text);
  };
};<|endoftext|>


<commit_before>import {never} from 'rxjs/observable/never'
import {_finally} from 'rxjs/operator/finally'
import {merge} from 'rxjs/operator/merge'
import {publishReplay} from 'rxjs/operator/publishReplay'

export default function cache(cache = {}) {
  return function cacheRequest(request, source) {
    const {path, method, params} = request

    if (method !== 'OBSERVE') {
      return source(request)
    }

    const key = path + JSON.stringify(params)

    // If there is nothing in the cache, add it.
    if (!cache[key]) {
      // Get observable from wrapped source.
      cache[key] = source(request)
        // Ignore complete by merging never.
        // We only want to remove from cache on unsubscribe.
        ::merge(never())
        // Remove from cache on unsubscribe or error.
        ::_finally(() => { delete cache[key] })
        // Share single copy of original observable.
        ::publishReplay(1).refCount()
    }

    return cache[key]
  }
}
<commit_msg>Only emit changes<commit_after>import {never} from 'rxjs/observable/never'
import {_finally} from 'rxjs/operator/finally'
import {merge} from 'rxjs/operator/merge'
import {publishReplay} from 'rxjs/operator/publishReplay'
import {distinctUntilChanged} from 'rxjs/operator/distinctUntilChanged'

export default function cache(cache = {}) {
  return function cacheRequest(request, source) {
    const {path, method, params} = request

    if (method !== 'OBSERVE') {
      return source(request)
    }

    const key = path + JSON.stringify(params)

    // If there is nothing in the cache, add it.
    if (!cache[key]) {
      // Get observable from wrapped source.
      cache[key] = source(request)
        // Only emit changes.
        ::distinctUntilChanged()
        // Ignore complete by merging never.
        // We only want to remove from cache on unsubscribe.
        ::merge(never())
        // Remove from cache on unsubscribe or error.
        ::_finally(() => { delete cache[key] })
        // Share single copy of original observable.
        ::publishReplay(1).refCount()
    }

    return cache[key]
  }
}
<|endoftext|>