<commit_before>class SocialApiController extends KDController

  constructor: (options = {}, data) ->

    @openedChannels = {}
    @_cache         = {}

    super options, data

  getPrefetchedData: (dataPath) ->

    return [] unless KD.socialApiData

    data = if dataPath is 'navigated'
    then KD.socialApiData[dataPath]?.data?.messageList
    else KD.socialApiData[dataPath]

    return [] unless data

    fn = switch dataPath
      when 'followedChannels' then mapChannels
      when 'popularPosts', 'pinnedMessages', 'navigated' then mapActivities
      when 'privateMessages'                   then mapPrivateMessages

    return fn(data) or []


  eachCached: (id, fn) ->
    fn section[id]  for own name, section of @_cache when id of section

  isAnnouncementItem: (channelId) ->
    return no   unless channelId

    # super admins can see/post anyting
    return no   if KD.checkFlag "super-admin"

    {socialApiAnnouncementChannelId} = KD.getGroup()

    return  channelId is socialApiAnnouncementChannelId

  onChannelReady: (channel, callback) ->
    channelName = generateChannelName channel
    if channel = @openedChannels[channelName]?.channel
    then callback channel
    else @once "ChannelRegistered-#{channelName}", callback

  mapActivity = (data) ->

    return  unless data
    return  unless plain = data.message

    {accountOldId, replies, interactions} = data
    {createdAt, deletedAt, updatedAt}     = plain

    plain._id = plain.id

    {payload} = plain

    m = new KD.remote.api.SocialMessage plain
    m.account = mapAccounts(accountOldId)[0]

    m.replies      = mapActivities data.replies or []
    m.repliesCount = data.repliesCount
    m.isFollowed   = data.isFollowed

    # this is sent by the server when
    # response for pinned messages
    m.unreadRepliesCount = data.unreadRepliesCount

    m.clientRequestId = plain.clientRequestId

    m.interactions    = interactions or
      like            :
        actorsCount   : 0
        actorsPreview : []
        isInteracted  : no

    if payload?.link_url
      m.link       =
        link_url   : payload.link_url
        link_embed :
          try JSON.parse Encoder.htmlDecode payload.link_embed
          catch e then null

    new MessageEventManager {}, m

    KD.singletons.socialapi.cacheItem m

    return m

  mapActivities = (messages)->
    # if no result, no need to do something
    return messages unless messages
    # get messagees from result set if they are not at the first level
    messages = messages.messageList if messages.messageList
    messages = [].concat(messages)
    revivedMessages = []
    {SocialMessage} = KD.remote.api
    revivedMessages = (mapActivity message for message in messages)
    return revivedMessages

  mapActivities: mapActivities

  getCurrentGroup = (callback)->
    groupsController = KD.getSingleton "groupsController"
    groupsController.ready ->
      callback  KD.getSingleton("groupsController").getCurrentGroup()

  mapPrivateMessages: mapPrivateMessages
  mapPrivateMessages = (messages)->
    messages = [].concat(messages)
    return [] unless messages?.length > 0

    mappedChannels = []

    for channelContainer in messages
      message             = mapActivity channelContainer.lastMessage
      channel             = mapChannel channelContainer
      channel.lastMessage = message

      mappedChannels.push channel

    registerAndOpenChannels mappedChannels

    return mappedChannels

  mapAccounts = (accounts)->
    return [] unless accounts
    mappedAccounts = []
    accounts = [].concat(accounts)

    for account in accounts
      mappedAccounts.push {_id: account, constructorName : "JAccount"}
    return mappedAccounts


  mapChannel = (channel) ->

    data                     = channel.channel
    data._id                 = data.id
    data.isParticipant       = channel.isParticipant
    data.participantCount    = channel.participantCount
    data.participantsPreview = mapAccounts channel.participantsPreview
    data.unreadCount         = channel.unreadCount
    data.lastMessage         = mapActivity channel.lastMessage  if channel.lastMessage


    channelInstance = new KD.remote.api.SocialChannel data

    KD.singletons.socialapi.cacheItem channelInstance

    return channelInstance

  mapParticipant = (participant) ->
    return  unless participant
    return {_id: participant.accountOldId, constructorName: "JAccount"}

  mapChannels = (channels)->

    return channels  unless channels

    channels        = [].concat channels
    revivedChannels = (mapChannel channel  for channel in channels)

    # bind all events
    registerAndOpenChannels revivedChannels

    return revivedChannels


  mapChannels: mapChannels



  forwardMessageEvents = (source, target,  events)->
    events.forEach ({event, mapperFn}) ->
      source.on event, (data, rest...) ->
        data = mapperFn data
        target.emit event, data, rest...

  forwardMessageEvents : forwardMessageEvents

  registerAndOpenChannels = (socialApiChannels)->
    {socialapi} = KD.singletons

    getCurrentGroup (group)->
      socialApiChannels.forEach (socialApiChannel) ->
        channelName = generateChannelName socialApiChannel
        return  if socialapi.openedChannels[channelName]
        socialapi.cacheItem socialApiChannel
        socialapi.openedChannels[channelName] = {} # placeholder to avoid duplicate registration

        subscriptionData =
          serviceType: 'socialapi'
          group      : group.slug
          channelType: socialApiChannel.typeConstant
          channelName: socialApiChannel.name
          isExclusive: yes
          connectDirectly: yes

        # do not use callbacks while subscribing, KD.remote.subscribe already
        # returns the required channel object. Use it. Callbacks are called
        # twice in the subscribe function
        brokerChannel = KD.remote.subscribe channelName, subscriptionData

        # add opened channel to the openedChannels list, for later use
        socialapi.openedChannels[channelName] = {delegate: brokerChannel, channel: socialApiChannel}

        # start forwarding private channel evetns to the original social channel
        forwardMessageEvents brokerChannel, socialApiChannel, getMessageEvents()

        # notify listener
        socialapi.emit "ChannelRegistered-#{channelName}", socialApiChannel


  cycleChannel: (channel, callback)->
    options =
      groupSlug     : channel.groupName
      apiChannelType: channel.typeConstant
      apiChannelName: channel.name
    KD.remote.api.SocialChannel.cycleChannel options, callback

  generateChannelName = ({name, typeConstant, groupName}) ->
    return "socialapi.#{groupName}-#{typeConstant}-#{name}"

  messageRequesterFn = (options)->
    options.apiType = "message"
    return requester options

  channelRequesterFn = (options)->
    options.apiType = "channel"
    return requester options

  notificationRequesterFn = (options)->
    options.apiType = "notification"
    return requester options

  requester = (req) ->
    (options, callback)->
      {fnName, validate, mapperFn, defaults, apiType} = req
      # set default mapperFn
      mapperFn or= (value) -> return value
      if validate?.length > 0
        errs = []
        for property in validate
          errs.push property unless options[property]
        if errs.length > 0
          msg = "#{errs.join(', ')} fields are required for #{fnName}"
          return callback {message: msg}

      _.defaults options, defaults  if defaults

      api = {}
      switch apiType
        when "channel"
          api = KD.remote.api.SocialChannel
        when "notification"
          api = KD.remote.api.SocialNotification
        else
          api = KD.remote.api.SocialMessage

      api[fnName] options, (err, result)->
        return callback err if err
        return callback null, mapperFn result

  cacheItem: (item) ->

    {typeConstant, id} = item

    @_cache[typeConstant]     ?= {}
    @_cache[typeConstant][id]  = item

    return item

  retrieveCachedItem: (type, id) ->

    return item  if item = @_cache[type]?[id]

    if type is 'topic'
      for own id_, topic of @_cache.topic when topic.name is id
        item = topic

    if not item and type is 'activity'
      for own id_, post of @_cache.post when post.slug is id
        item = post

    return item


  cacheable: (type, id, force, callback) ->
    [callback, force] = [force, no]  unless callback

    if not force and item = @retrieveCachedItem(type, id)

      return callback null, item

    kallback = (err, data) =>
      return callback err  if err

      callback null, @cacheItem data

    topicChannelKallback = (err, data) =>
      return callback err  if err

      registerAndOpenChannels [data]
      kallback err, data

    return switch type
      when 'topic'                     then @channel.byName {name: id}, topicChannelKallback
      when 'activity'                  then @message.bySlug {slug: id}, kallback
      when 'channel', 'privatemessage' then @channel.byId {id}, topicChannelKallback
      when 'post', 'message'           then @message.byId {id}, kallback
      else callback { message: "#{type} not implemented in revive" }

  getMessageEvents = ->
    [
      {event: "MessageAdded", mapperFn: mapActivity}
      {event: "MessageRemoved", mapperFn: mapActivity}
      {event: "AddedToChannel", mapperFn: mapParticipant}
      {event: "ChannelDeleted", mapperFn: mapChannel}
    ]

  serialize = (obj) ->
    str = []
    for own p of obj
      str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]))

    return str.join "&"

  message:
    byId                 : messageRequesterFn
      fnName             : 'byId'
      validateOptionsWith: ['id']
      mapperFn           : mapActivity

    bySlug               : messageRequesterFn
      fnName             : 'bySlug'
      validateOptionsWith: ['slug']
      mapperFn           : mapActivity<commit_msg>socialapi: add validation method to message events<commit_after>class SocialApiController extends KDController

  constructor: (options = {}, data) ->

    @openedChannels = {}
    @_cache         = {}

    super options, data

  getPrefetchedData: (dataPath) ->

    return [] unless KD.socialApiData

    data = if dataPath is 'navigated'
    then KD.socialApiData[dataPath]?.data?.messageList
    else KD.socialApiData[dataPath]

    return [] unless data

    fn = switch dataPath
      when 'followedChannels' then mapChannels
      when 'popularPosts', 'pinnedMessages', 'navigated' then mapActivities
      when 'privateMessages'                   then mapPrivateMessages

    return fn(data) or []


  eachCached: (id, fn) ->
    fn section[id]  for own name, section of @_cache when id of section

  isAnnouncementItem: (channelId) ->
    return no   unless channelId

    # super admins can see/post anyting
    return no   if KD.checkFlag "super-admin"

    {socialApiAnnouncementChannelId} = KD.getGroup()

    return  channelId is socialApiAnnouncementChannelId

  onChannelReady: (channel, callback) ->
    channelName = generateChannelName channel
    if channel = @openedChannels[channelName]?.channel
    then callback channel
    else @once "ChannelRegistered-#{channelName}", callback

  mapActivity = (data) ->

    return  unless data
    return  unless plain = data.message

    {accountOldId, replies, interactions} = data
    {createdAt, deletedAt, updatedAt}     = plain

    plain._id = plain.id

    {payload} = plain

    m = new KD.remote.api.SocialMessage plain
    m.account = mapAccounts(accountOldId)[0]

    m.replies      = mapActivities data.replies or []
    m.repliesCount = data.repliesCount
    m.isFollowed   = data.isFollowed

    # this is sent by the server when
    # response for pinned messages
    m.unreadRepliesCount = data.unreadRepliesCount

    m.clientRequestId = plain.clientRequestId

    m.interactions    = interactions or
      like            :
        actorsCount   : 0
        actorsPreview : []
        isInteracted  : no

    if payload?.link_url
      m.link       =
        link_url   : payload.link_url
        link_embed :
          try JSON.parse Encoder.htmlDecode payload.link_embed
          catch e then null

    new MessageEventManager {}, m

    KD.singletons.socialapi.cacheItem m

    return m

  mapActivities = (messages)->
    # if no result, no need to do something
    return messages unless messages
    # get messagees from result set if they are not at the first level
    messages = messages.messageList if messages.messageList
    messages = [].concat(messages)
    revivedMessages = []
    {SocialMessage} = KD.remote.api
    revivedMessages = (mapActivity message for message in messages)
    return revivedMessages

  mapActivities: mapActivities

  getCurrentGroup = (callback)->
    groupsController = KD.getSingleton "groupsController"
    groupsController.ready ->
      callback  KD.getSingleton("groupsController").getCurrentGroup()

  mapPrivateMessages: mapPrivateMessages
  mapPrivateMessages = (messages)->
    messages = [].concat(messages)
    return [] unless messages?.length > 0

    mappedChannels = []

    for channelContainer in messages
      message             = mapActivity channelContainer.lastMessage
      channel             = mapChannel channelContainer
      channel.lastMessage = message

      mappedChannels.push channel

    registerAndOpenChannels mappedChannels

    return mappedChannels

  mapAccounts = (accounts)->
    return [] unless accounts
    mappedAccounts = []
    accounts = [].concat(accounts)

    for account in accounts
      mappedAccounts.push {_id: account, constructorName : "JAccount"}
    return mappedAccounts


  mapChannel = (channel) ->

    data                     = channel.channel
    data._id                 = data.id
    data.isParticipant       = channel.isParticipant
    data.participantCount    = channel.participantCount
    data.participantsPreview = mapAccounts channel.participantsPreview
    data.unreadCount         = channel.unreadCount
    data.lastMessage         = mapActivity channel.lastMessage  if channel.lastMessage


    channelInstance = new KD.remote.api.SocialChannel data

    KD.singletons.socialapi.cacheItem channelInstance

    return channelInstance

  mapParticipant = (participant) ->
    return  unless participant
    return {_id: participant.accountOldId, constructorName: "JAccount"}

  mapChannels = (channels)->

    return channels  unless channels

    channels        = [].concat channels
    revivedChannels = (mapChannel channel  for channel in channels)

    # bind all events
    registerAndOpenChannels revivedChannels

    return revivedChannels


  mapChannels: mapChannels


  # this method will prevent the arrival of
  # realtime messages to the individual messages
  # if the message is mine and current window has focus.
  isFromThisBrowser = (message) ->

    isMyPost  = KD.isMyPost message
    isFocused = KD.singletons.windowController.isFocused()
    isBlocker = isMyPost and isFocused

    return not isBlocker

  isFromThisBrowser : isFromThisBrowser

  forwardMessageEvents = (source, target,  events)->
    events.forEach ({event, mapperFn}) ->
      source.on event, (data, rest...) ->
        data = mapperFn data
        target.emit event, data, rest...

  forwardMessageEvents : forwardMessageEvents

  registerAndOpenChannels = (socialApiChannels)->
    {socialapi} = KD.singletons

    getCurrentGroup (group)->
      socialApiChannels.forEach (socialApiChannel) ->
        channelName = generateChannelName socialApiChannel
        return  if socialapi.openedChannels[channelName]
        socialapi.cacheItem socialApiChannel
        socialapi.openedChannels[channelName] = {} # placeholder to avoid duplicate registration

        subscriptionData =
          serviceType: 'socialapi'
          group      : group.slug
          channelType: socialApiChannel.typeConstant
          channelName: socialApiChannel.name
          isExclusive: yes
          connectDirectly: yes

        # do not use callbacks while subscribing, KD.remote.subscribe already
        # returns the required channel object. Use it. Callbacks are called
        # twice in the subscribe function
        brokerChannel = KD.remote.subscribe channelName, subscriptionData

        # add opened channel to the openedChannels list, for later use
        socialapi.openedChannels[channelName] = {delegate: brokerChannel, channel: socialApiChannel}

        # start forwarding private channel evetns to the original social channel
        forwardMessageEvents brokerChannel, socialApiChannel, getMessageEvents()

        # notify listener
        socialapi.emit "ChannelRegistered-#{channelName}", socialApiChannel


  cycleChannel: (channel, callback)->
    options =
      groupSlug     : channel.groupName
      apiChannelType: channel.typeConstant
      apiChannelName: channel.name
    KD.remote.api.SocialChannel.cycleChannel options, callback

  generateChannelName = ({name, typeConstant, groupName}) ->
    return "socialapi.#{groupName}-#{typeConstant}-#{name}"

  messageRequesterFn = (options)->
    options.apiType = "message"
    return requester options

  channelRequesterFn = (options)->
    options.apiType = "channel"
    return requester options

  notificationRequesterFn = (options)->
    options.apiType = "notification"
    return requester options

  requester = (req) ->
    (options, callback)->
      {fnName, validate, mapperFn, defaults, apiType} = req
      # set default mapperFn
      mapperFn or= (value) -> return value
      if validate?.length > 0
        errs = []
        for property in validate
          errs.push property unless options[property]
        if errs.length > 0
          msg = "#{errs.join(', ')} fields are required for #{fnName}"
          return callback {message: msg}

      _.defaults options, defaults  if defaults

      api = {}
      switch apiType
        when "channel"
          api = KD.remote.api.SocialChannel
        when "notification"
          api = KD.remote.api.SocialNotification
        else
          api = KD.remote.api.SocialMessage

      api[fnName] options, (err, result)->
        return callback err if err
        return callback null, mapperFn result

  cacheItem: (item) ->

    {typeConstant, id} = item

    @_cache[typeConstant]     ?= {}
    @_cache[typeConstant][id]  = item

    return item

  retrieveCachedItem: (type, id) ->

    return item  if item = @_cache[type]?[id]

    if type is 'topic'
      for own id_, topic of @_cache.topic when topic.name is id
        item = topic

    if not item and type is 'activity'
      for own id_, post of @_cache.post when post.slug is id
        item = post

    return item


  cacheable: (type, id, force, callback) ->
    [callback, force] = [force, no]  unless callback

    if not force and item = @retrieveCachedItem(type, id)

      return callback null, item

    kallback = (err, data) =>
      return callback err  if err

      callback null, @cacheItem data

    topicChannelKallback = (err, data) =>
      return callback err  if err

      registerAndOpenChannels [data]
      kallback err, data

    return switch type
      when 'topic'                     then @channel.byName {name: id}, topicChannelKallback
      when 'activity'                  then @message.bySlug {slug: id}, kallback
      when 'channel', 'privatemessage' then @channel.byId {id}, topicChannelKallback
      when 'post', 'message'           then @message.byId {id}, kallback
      else callback { message: "#{type} not implemented in revive" }

  getMessageEvents = ->
    [
      {event: "MessageAdded",   mapperFn: mapActivity, validatorFn: isFromThisBrowser}
      {event: "MessageRemoved", mapperFn: mapActivity, validatorFn: isFromThisBrowser}
      {event: "AddedToChannel", mapperFn: mapParticipant}
      {event: "ChannelDeleted", mapperFn: mapChannel}
    ]

  serialize = (obj) ->
    str = []
    for own p of obj
      str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]))

    return str.join "&"

  message:
    byId                 : messageRequesterFn
      fnName             : 'byId'
      validateOptionsWith: ['id']
      mapperFn           : mapActivity

    bySlug               : messageRequesterFn
      fnName             : 'bySlug'
      validateOptionsWith: ['slug']
      mapperFn           : mapActivity
{'fraction_non_alphanumeric': 0.08100449482118428, 'fraction_numerical': 0.0004885675200312683, 'mean_word_length': 2.695432388517783, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 82, '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': '352121', 'n_tokens_mistral': 5713, 'n_tokens_neox': 5448, 'n_words': 1584}