<commit_before>import { Posts } from './collection';
import { getSetting } from 'meteor/vulcan:core';
import Users from "meteor/vulcan:users";
import { makeEditable } from '../../editor/make_editable.js'
import { addFieldsDict, foreignKeyField, arrayOfForeignKeysField, accessFilterMultiple, resolverOnlyField } from '../../modules/utils/schemaUtils'
import { localGroupTypeFormOptions } from '../localgroups/groupTypes';
import { Utils } from 'meteor/vulcan:core';
import GraphQLJSON from 'graphql-type-json';
import { Comments } from '../comments'
import { questionAnswersSort } from '../comments/views';
import { schemaDefaultValue } from '../../collectionUtils';
import { getWithCustomLoader } from '../../loaders.js';
import keyBy from 'lodash/keyBy';

export const formGroups = {
  adminOptions: {
    name: "adminOptions",
    order: 25,
    label: "Admin Options",
    startCollapsed: true,
  },
  event: {
    name: "event details",
    order: 21,
    label: "Event Details"
  },
  moderationGroup: {
    order: 60,
    name: "moderation",
    label: "Moderation Guidelines",
    helpText: "We prefill these moderation guidelines based on your user settings. But you can adjust them for each post.",
    startCollapsed: true,
  },
  options: {
    order:10,
    name: "options",
    defaultStyle: true,
    flexStyle: true
  },
  content: { //TODO – should this be 'contents'? is it needed?
    order:20,
    name: "Content",
    defaultStyle: true,
  },
  canonicalSequence: {
    order:30,
    name: "canonicalSequence",
    label: "Canonical Sequence",
    startCollapsed: true,
  },
  advancedOptions: {
    order:40,
    name: "advancedOptions",
    label: "Options",
    startCollapsed: true,
    flexStyle: true
  },
};


const userHasModerationGuidelines = (currentUser) => {
  return !!(currentUser && ((currentUser.moderationGuidelines && currentUser.moderationGuidelines.html) || currentUser.moderationStyle))
}

addFieldsDict(Posts, {
  // URL (Overwriting original schema)
  url: {
    order: 12,
    control: 'EditUrl',
    placeholder: 'Add a linkpost URL',
    group: formGroups.options,
    editableBy: [Users.owns, 'sunshineRegiment', 'admins']
  },
  // Title (Overwriting original schema)
  title: {
    order: 10,
    placeholder: "Title",
    control: 'EditTitle',
    editableBy: [Users.owns, 'sunshineRegiment', 'admins']
  },

  // Legacy: Boolean used to indicate that post was imported from old LW database
  legacy: {
    type: Boolean,
    optional: true,
    hidden: false,
    defaultValue: false,
    viewableBy: ['guests'],
    editableBy: ['admin'],
    insertableBy: ['admin'],
    control: "checkbox",
    order: 12,
    group: formGroups.adminOptions,
  },

  // Legacy ID: ID used in the original LessWrong database
  legacyId: {
    type: String,
    optional: true,
    hidden: true,
    viewableBy: ['guests'],
    editableBy: ['members'],
    insertableBy: ['members'],
  },

  // Legacy Spam: True if the original post in the legacy LW database had this post
  // marked as spam
  legacySpam: {
    type: Boolean,
    optional: true,
    defaultValue: false,
    hidden: true,
    viewableBy: ['guests'],
    editableBy: ['members'],
    insertableBy: ['members'],
  },

  // Feed Id: If this post was automatically generated by an integrated RSS feed
  // then this field will have the ID of the relevant feed
  feedId: {
    ...foreignKeyField({
      idFieldName: "feedId",
      resolverName: "feed",
      collectionName: "RSSFeeds",
      type: "RSSFeed"
    }),
    optional: true,
    viewableBy: ['guests'],
    editableBy: ['admins'],
    insertableBy: ['admins'],
    group: formGroups.adminOptions,
  },

  // Feed Link: If this post was automatically generated by an integrated RSS feed
  // then this field will have the link to the original blogpost it was posted from
  feedLink: {
    type: String,
    optional: true,
    viewableBy: ['guests'],
    editableBy: ['admins'],
    insertableBy: ['admins'],
    group: formGroups.adminOptions
  },

  // lastVisitedAt: If the user is logged in and has viewed this post, the date
  // they last viewed it. Otherwise, null.
  lastVisitedAt: resolverOnlyField({
    type: Date,
    viewableBy: ['guests'],
    resolver: async (post, args, { ReadStatuses, currentUser }) => {
      if (!currentUser) return null;
      
      return await getWithCustomLoader(ReadStatuses, `postsReadByUser${currentUser._id}`, post._id,
        async postIDs => {
          const readDates = await ReadStatuses.find({
            userId: currentUser._id,
            postId: {$in: postIDs},
            isRead: true,
          }, {postId:1, lastUpdated:1}).fetch();
          
          const readDateByPostID = keyBy(readDates, ev=>ev.postId);
          return postIDs.map(postID => readDateByPostID[postID] ? readDateByPostID[postID].lastUpdated : null);
        }
      );
    }
  }),

  lastCommentedAt: {
    type: Date,
    denormalized: true,
    optional: true,
    hidden: true,
    viewableBy: ['guests'],
    onInsert: () => new Date(),
  },

  // curatedDate: Date at which the post was promoted to curated (null or false
  // if it never has been promoted to curated)
  curatedDate: {
    type: Date,
    optional: true,
    viewableBy: ['guests'],
    insertableBy: ['sunshineRegiment', 'admins'],
    editableBy: ['sunshineRegiment', 'admins'],
    group: formGroups.adminOptions,
  },
  // metaDate: Date at which the post was marked as meta (null or false if it
  // never has been marked as meta)
  metaDate: {
    type: Date,<commit_msg>Use old method, for incremental switchover<commit_after>import { Posts } from './collection';
import { getSetting } from 'meteor/vulcan:core';
import Users from "meteor/vulcan:users";
import { makeEditable } from '../../editor/make_editable.js'
import { addFieldsDict, foreignKeyField, arrayOfForeignKeysField, accessFilterMultiple, resolverOnlyField } from '../../modules/utils/schemaUtils'
import { localGroupTypeFormOptions } from '../localgroups/groupTypes';
import { Utils } from 'meteor/vulcan:core';
import GraphQLJSON from 'graphql-type-json';
import { Comments } from '../comments'
import { questionAnswersSort } from '../comments/views';
import { schemaDefaultValue } from '../../collectionUtils';
import { getWithCustomLoader } from '../../loaders.js';
import keyBy from 'lodash/keyBy';

export const formGroups = {
  adminOptions: {
    name: "adminOptions",
    order: 25,
    label: "Admin Options",
    startCollapsed: true,
  },
  event: {
    name: "event details",
    order: 21,
    label: "Event Details"
  },
  moderationGroup: {
    order: 60,
    name: "moderation",
    label: "Moderation Guidelines",
    helpText: "We prefill these moderation guidelines based on your user settings. But you can adjust them for each post.",
    startCollapsed: true,
  },
  options: {
    order:10,
    name: "options",
    defaultStyle: true,
    flexStyle: true
  },
  content: { //TODO – should this be 'contents'? is it needed?
    order:20,
    name: "Content",
    defaultStyle: true,
  },
  canonicalSequence: {
    order:30,
    name: "canonicalSequence",
    label: "Canonical Sequence",
    startCollapsed: true,
  },
  advancedOptions: {
    order:40,
    name: "advancedOptions",
    label: "Options",
    startCollapsed: true,
    flexStyle: true
  },
};


const userHasModerationGuidelines = (currentUser) => {
  return !!(currentUser && ((currentUser.moderationGuidelines && currentUser.moderationGuidelines.html) || currentUser.moderationStyle))
}

addFieldsDict(Posts, {
  // URL (Overwriting original schema)
  url: {
    order: 12,
    control: 'EditUrl',
    placeholder: 'Add a linkpost URL',
    group: formGroups.options,
    editableBy: [Users.owns, 'sunshineRegiment', 'admins']
  },
  // Title (Overwriting original schema)
  title: {
    order: 10,
    placeholder: "Title",
    control: 'EditTitle',
    editableBy: [Users.owns, 'sunshineRegiment', 'admins']
  },

  // Legacy: Boolean used to indicate that post was imported from old LW database
  legacy: {
    type: Boolean,
    optional: true,
    hidden: false,
    defaultValue: false,
    viewableBy: ['guests'],
    editableBy: ['admin'],
    insertableBy: ['admin'],
    control: "checkbox",
    order: 12,
    group: formGroups.adminOptions,
  },

  // Legacy ID: ID used in the original LessWrong database
  legacyId: {
    type: String,
    optional: true,
    hidden: true,
    viewableBy: ['guests'],
    editableBy: ['members'],
    insertableBy: ['members'],
  },

  // Legacy Spam: True if the original post in the legacy LW database had this post
  // marked as spam
  legacySpam: {
    type: Boolean,
    optional: true,
    defaultValue: false,
    hidden: true,
    viewableBy: ['guests'],
    editableBy: ['members'],
    insertableBy: ['members'],
  },

  // Feed Id: If this post was automatically generated by an integrated RSS feed
  // then this field will have the ID of the relevant feed
  feedId: {
    ...foreignKeyField({
      idFieldName: "feedId",
      resolverName: "feed",
      collectionName: "RSSFeeds",
      type: "RSSFeed"
    }),
    optional: true,
    viewableBy: ['guests'],
    editableBy: ['admins'],
    insertableBy: ['admins'],
    group: formGroups.adminOptions,
  },

  // Feed Link: If this post was automatically generated by an integrated RSS feed
  // then this field will have the link to the original blogpost it was posted from
  feedLink: {
    type: String,
    optional: true,
    viewableBy: ['guests'],
    editableBy: ['admins'],
    insertableBy: ['admins'],
    group: formGroups.adminOptions
  },

  // lastVisitedAt: If the user is logged in and has viewed this post, the date
  // they last viewed it. Otherwise, null.
  lastVisitedAt: resolverOnlyField({
    type: Date,
    viewableBy: ['guests'],
    resolver: async (post, args, { ReadStatuses, currentUser }) => {
      if (!currentUser) return null;
      
      // Slow method, from the LWEvents table. This is an N+1 select. Switch
      // after the migration script is run.
      const event = await LWEvents.findOne({name:'post-view', documentId: post._id, userId: currentUser._id}, {sort:{createdAt:-1}});
      return event && event.createdAt
      
      // Faster method, using the ReadStatuses table.
      /*return await getWithCustomLoader(ReadStatuses, `postsReadByUser${currentUser._id}`, post._id,
        async postIDs => {
          const readDates = await ReadStatuses.find({
            userId: currentUser._id,
            postId: {$in: postIDs},
            isRead: true,
          }, {postId:1, lastUpdated:1}).fetch();
          
          const readDateByPostID = keyBy(readDates, ev=>ev.postId);
          return postIDs.map(postID => readDateByPostID[postID] ? readDateByPostID[postID].lastUpdated : null);
        }
      );*/
    }
  }),

  lastCommentedAt: {
    type: Date,
    denormalized: true,
    optional: true,
    hidden: true,
    viewableBy: ['guests'],
    onInsert: () => new Date(),
  },

  // curatedDate: Date at which the post was promoted to curated (null or false
  // if it never has been promoted to curated)
  curatedDate: {
    type: Date,
    optional: true,
    viewableBy: ['guests'],
    insertableBy: ['sunshineRegiment', 'admins'],
    editableBy: ['sunshineRegiment', 'admins'],
    group: formGroups.adminOptions,
  },
  // metaDate: Date at which the post was marked as meta (null or false if it
  // never has been marked as meta)
  metaDate: {
    type: Date,
{'fraction_non_alphanumeric': 0.12585951779963445, 'fraction_numerical': 0.00400382975019584, 'mean_word_length': 3.1301222142343637, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, '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': '7484563', 'n_tokens_mistral': 3584, 'n_tokens_neox': 3471, 'n_words': 1052}