<commit_before>import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { Match, check } from 'meteor/check';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
import faker from 'faker';

import { SchemaHelpers } from './common';
import './users';

export const Actions = new Mongo.Collection('actions');

Actions.after.remove((userId, doc) => Actions.remove({ objectId: doc._id }));

const updateParentActionCounter = isInc => (_, action) => {
  const d = isInc ? 1 : -1;
  switch (action.type) {
    case Actions.types.COMMENT:
      Actions.update(action.objectId, {$inc: {commentsCount: d}});
      break;
    case Actions.types.RATE:
      action.rate && Actions.update(action.objectId, {$inc: {['rates.' + (action.rate == 1 ? 'up' : 'down')]: d}});
      break;
    case Actions.types.SHARE:
      Actions.update(action.objectId, {$inc: {sharesCount: d}});
      break;
  }
};

Actions.after.insert(updateParentActionCounter(true));
Actions.after.remove(updateParentActionCounter(false));

Actions.types = {
  DEFAULT: 'default',
  SUBSCRIBE: 'subscribe',
  COMMENT: 'comment',
  RATE: 'rate',
  SHARE: 'share',
};

Actions.relevantTypes = [Actions.types.DEFAULT, Actions.types.SHARE];

Actions.typesTree = {
  'Записи': {
    'Обычные': Actions.types.DEFAULT, 'Репосты': Actions.types.SHARE,
  },
  'Ответы': {
    'Подписки': Actions.types.SUBSCRIBE, 'Оценки': Actions.types.RATE, 'Комментарии': Actions.types.COMMENT,
  },
};

Actions.notificationTypesTree = {
  'Репосты': Actions.types.SHARE,
  'Подписки': Actions.types.SUBSCRIBE,
  'Оценки': Actions.types.RATE,
  'Комментарии': Actions.types.COMMENT,
};

Actions.schema = new SimpleSchema({
  _id: { type: String, regEx: SimpleSchema.RegEx.Id, denyUpdate: true },
  userId: { type: String, regEx: SimpleSchema.RegEx.Id, denyUpdate: true },
  objectId: { type: String, regEx: SimpleSchema.RegEx.Id, denyUpdate: true, optional: true },
  description: { type: String, optional: true },
  type: { type: String, defaultValue: Actions.types.DEFAULT, denyUpdate: true },

  commentsCount: { type: Number, defaultValue: 0, optional: true },

  rates: { type: Object, optional: true },
  'rates.up': { type: Number, defaultValue: 0 },
  'rates.down': { type: Number, defaultValue: 0 },
  rate: { type: Number, optional: true, denyUpdate: true },

  sharesCount: { type: Number, defaultValue: 0, optional: true },

  createdAt: SchemaHelpers.createdAt,
  updatedAt: SchemaHelpers.updatedAt,
  deletedAt: SchemaHelpers.deletedAt,
});

Actions.attachSchema(Actions.schema);

if (Meteor.isServer) {
  const actionUsersCursor = {
    find: action => {
      const userIds = [action.userId];
      if (action.type == Actions.types.SUBSCRIBE)
        userIds.push(action.objectId);
      return Meteor.users.find({_id: {$in: userIds}}, {fields: Meteor.users.publicFields});
    }
  };

  const actionUserReactionCursor = {
    find: function(action) {
      return Actions.find({
        type: {$in: [Actions.types.RATE, Actions.types.SHARE]},
        objectId: action._id, userId: this.userId
      })
    }
  };

  const actionCommentsCursor = {
    find: action => Actions.find({
      type: Actions.types.COMMENT, objectId: action._id,
    }),
    children: [actionUsersCursor, actionUserReactionCursor],
  };

  const actionParentCursor = {
    find: action => {
      if ([Actions.types.COMMENT, Actions.types.RATE, Actions.types.SHARE].includes(action.type))
        return Actions.find(action.objectId);
    },
    children: [actionUsersCursor],
  };
  actionParentCursor.children.push(actionParentCursor);

  export const actionChildrenCursors = [
    actionUsersCursor,
    actionUserReactionCursor,
    actionCommentsCursor,
    actionParentCursor,
  ];

  Meteor.publishComposite('actions', function (selector, limit) {
    check(selector, Object);
    check(limit, Match.Integer);

    Counts.publish(this, 'actions', Actions.find(selector));
    return {
      find: () => Actions.find(selector, { sort: {createdAt: -1}, limit: limit }),
      children: actionChildrenCursors,
    };
  });
}

const joinActionUsers = action => {
  action.user = Meteor.users.findOne(action.userId);
  if (!action.user) return null;
  if (action.type == Actions.types.SUBSCRIBE) {
    action.object = Meteor.users.findOne(action.objectId);
    if (!action.object) return null;
  }
  return action;
};

const joinActionUserReaction = action => {
  if (Meteor.isServer) return action;
  const rateAction = Actions.findOne({
    type: Actions.types.RATE,
    objectId: action._id, userId: Meteor.userId()
  });
  if (rateAction) action.currentUserRate = rateAction.rate;
  const shareAction = Actions.findOne({
    type: Actions.types.SHARE,
    objectId: action._id, userId: Meteor.userId()
  });
  action.currentUserShared = !!shareAction;
  return action;
};

const joinActionComments = action => {
  action.comments = Actions.find({
    type: Actions.types.COMMENT, objectId: action._id
  }, {sort: {createdAt: 1}}).fetch().filter(joinActionUsers);
  action.comments.forEach(joinActionUserReaction);
  return action;
};

const joinActionParent = action => {
  if ([Actions.types.COMMENT, Actions.types.RATE, Actions.types.SHARE].includes(action.type)) {
    action.object = Actions.findOne(action.objectId);
    const valid = [joinActionUsers, joinActionParent]
      .reduce((p, join) => p && join(action.object), !!action.object);
    if (!valid) return null;
  }
  return action;
};

export const joinAction = action => {
  const valid = [joinActionUsers, joinActionUserReaction, joinActionComments, joinActionParent]
    .reduce((p, join) => p && join(action), !!action);
  return valid ? action : null;
};

Meteor.methods({
  'action.create' (description, type = Actions.types.DEFAULT) {
    check(description, String);
    check(description, Match.Where(a => a.length));
    check(type, Match.Where(a => [Actions.types.DEFAULT].includes(a)));

    if (!this.userId)
      throw new Meteor.Error('not-authorized');

    return Actions.insert({
      description,
      type,
      userId: this.userId,
    });
  },

  'action.remove' (actionId) {
    check(actionId, String);

    if (!this.userId)
      throw new Meteor.Error('not-authorized');
    const action = Actions.findOne(actionId);
    if (!action)
      throw new Meteor.Error('action-not-found');
    if (action.userId != this.userId)
      throw new Meteor.Error('forbidden');

    Actions.remove(actionId);
  },

  'action.subscribe' (userId) {
    check(userId, String);
<commit_msg>actions: Store array with action types what can be created like default action<commit_after>import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { Match, check } from 'meteor/check';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
import faker from 'faker';

import { SchemaHelpers } from './common';
import './users';

export const Actions = new Mongo.Collection('actions');

Actions.after.remove((userId, doc) => Actions.remove({ objectId: doc._id }));

const updateParentActionCounter = isInc => (_, action) => {
  const d = isInc ? 1 : -1;
  switch (action.type) {
    case Actions.types.COMMENT:
      Actions.update(action.objectId, {$inc: {commentsCount: d}});
      break;
    case Actions.types.RATE:
      action.rate && Actions.update(action.objectId, {$inc: {['rates.' + (action.rate == 1 ? 'up' : 'down')]: d}});
      break;
    case Actions.types.SHARE:
      Actions.update(action.objectId, {$inc: {sharesCount: d}});
      break;
  }
};

Actions.after.insert(updateParentActionCounter(true));
Actions.after.remove(updateParentActionCounter(false));

Actions.types = {
  DEFAULT: 'default',
  SUBSCRIBE: 'subscribe',
  COMMENT: 'comment',
  RATE: 'rate',
  SHARE: 'share',
};

Actions.relevantTypes = [Actions.types.DEFAULT, Actions.types.SHARE];
Actions.simpleTypes = [Actions.types.DEFAULT];

Actions.typesTree = {
  'Записи': {
    'Обычные': Actions.types.DEFAULT, 'Репосты': Actions.types.SHARE,
  },
  'Ответы': {
    'Подписки': Actions.types.SUBSCRIBE, 'Оценки': Actions.types.RATE, 'Комментарии': Actions.types.COMMENT,
  },
};

Actions.notificationTypesTree = {
  'Репосты': Actions.types.SHARE,
  'Подписки': Actions.types.SUBSCRIBE,
  'Оценки': Actions.types.RATE,
  'Комментарии': Actions.types.COMMENT,
};

Actions.schema = new SimpleSchema({
  _id: { type: String, regEx: SimpleSchema.RegEx.Id, denyUpdate: true },
  userId: { type: String, regEx: SimpleSchema.RegEx.Id, denyUpdate: true },
  objectId: { type: String, regEx: SimpleSchema.RegEx.Id, denyUpdate: true, optional: true },
  description: { type: String, optional: true },
  type: { type: String, defaultValue: Actions.types.DEFAULT, denyUpdate: true },

  commentsCount: { type: Number, defaultValue: 0, optional: true },

  rates: { type: Object, optional: true },
  'rates.up': { type: Number, defaultValue: 0 },
  'rates.down': { type: Number, defaultValue: 0 },
  rate: { type: Number, optional: true, denyUpdate: true },

  sharesCount: { type: Number, defaultValue: 0, optional: true },

  createdAt: SchemaHelpers.createdAt,
  updatedAt: SchemaHelpers.updatedAt,
  deletedAt: SchemaHelpers.deletedAt,
});

Actions.attachSchema(Actions.schema);

if (Meteor.isServer) {
  const actionUsersCursor = {
    find: action => {
      const userIds = [action.userId];
      if (action.type == Actions.types.SUBSCRIBE)
        userIds.push(action.objectId);
      return Meteor.users.find({_id: {$in: userIds}}, {fields: Meteor.users.publicFields});
    }
  };

  const actionUserReactionCursor = {
    find: function(action) {
      return Actions.find({
        type: {$in: [Actions.types.RATE, Actions.types.SHARE]},
        objectId: action._id, userId: this.userId
      })
    }
  };

  const actionCommentsCursor = {
    find: action => Actions.find({
      type: Actions.types.COMMENT, objectId: action._id,
    }),
    children: [actionUsersCursor, actionUserReactionCursor],
  };

  const actionParentCursor = {
    find: action => {
      if ([Actions.types.COMMENT, Actions.types.RATE, Actions.types.SHARE].includes(action.type))
        return Actions.find(action.objectId);
    },
    children: [actionUsersCursor],
  };
  actionParentCursor.children.push(actionParentCursor);

  export const actionChildrenCursors = [
    actionUsersCursor,
    actionUserReactionCursor,
    actionCommentsCursor,
    actionParentCursor,
  ];

  Meteor.publishComposite('actions', function (selector, limit) {
    check(selector, Object);
    check(limit, Match.Integer);

    Counts.publish(this, 'actions', Actions.find(selector));
    return {
      find: () => Actions.find(selector, { sort: {createdAt: -1}, limit: limit }),
      children: actionChildrenCursors,
    };
  });
}

const joinActionUsers = action => {
  action.user = Meteor.users.findOne(action.userId);
  if (!action.user) return null;
  if (action.type == Actions.types.SUBSCRIBE) {
    action.object = Meteor.users.findOne(action.objectId);
    if (!action.object) return null;
  }
  return action;
};

const joinActionUserReaction = action => {
  if (Meteor.isServer) return action;
  const rateAction = Actions.findOne({
    type: Actions.types.RATE,
    objectId: action._id, userId: Meteor.userId()
  });
  if (rateAction) action.currentUserRate = rateAction.rate;
  const shareAction = Actions.findOne({
    type: Actions.types.SHARE,
    objectId: action._id, userId: Meteor.userId()
  });
  action.currentUserShared = !!shareAction;
  return action;
};

const joinActionComments = action => {
  action.comments = Actions.find({
    type: Actions.types.COMMENT, objectId: action._id
  }, {sort: {createdAt: 1}}).fetch().filter(joinActionUsers);
  action.comments.forEach(joinActionUserReaction);
  return action;
};

const joinActionParent = action => {
  if ([Actions.types.COMMENT, Actions.types.RATE, Actions.types.SHARE].includes(action.type)) {
    action.object = Actions.findOne(action.objectId);
    const valid = [joinActionUsers, joinActionParent]
      .reduce((p, join) => p && join(action.object), !!action.object);
    if (!valid) return null;
  }
  return action;
};

export const joinAction = action => {
  const valid = [joinActionUsers, joinActionUserReaction, joinActionComments, joinActionParent]
    .reduce((p, join) => p && join(action), !!action);
  return valid ? action : null;
};

Meteor.methods({
  'action.create' (description, type = Actions.types.DEFAULT) {
    check(description, String);
    check(description, Match.Where(a => a.length));
    check(type, Match.Where(a => Actions.simpleTypes.includes(a)));

    if (!this.userId)
      throw new Meteor.Error('not-authorized');

    return Actions.insert({
      description,
      type,
      userId: this.userId,
    });
  },

  'action.remove' (actionId) {
    check(actionId, String);

    if (!this.userId)
      throw new Meteor.Error('not-authorized');
    const action = Actions.findOne(actionId);
    if (!action)
      throw new Meteor.Error('action-not-found');
    if (action.userId != this.userId)
      throw new Meteor.Error('forbidden');

    Actions.remove(actionId);
  },

  'action.subscribe' (userId) {
    check(userId, String);

{'fraction_non_alphanumeric': 0.14967379760279167, 'fraction_numerical': 0.0013654984069185253, 'mean_word_length': 3.908041697691735, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2321368', 'n_tokens_mistral': 4344, 'n_tokens_neox': 4220, 'n_words': 898}