<commit_before>#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

# @API Discussion Topics
#
# API for accessing and participating in discussion topics in groups and courses.
class DiscussionTopicsApiController < ApplicationController
  include Api::V1::DiscussionTopics
  include Api::V1::User

  before_filter :require_context
  before_filter :require_topic
  before_filter :require_initial_post, except: [:add_entry, :mark_topic_read,
                                                :mark_topic_unread, :show,
                                                :unsubscribe_topic]

  # @API Get a single topic
  #
  # Returns data on an individual discussion topic. See the List action for the response formatting.
  #
  # @example_request
  #
  #     curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics/<topic_id> \ 
  #         -H 'Authorization: Bearer <token>'
  def show
    render(json: discussion_topics_api_json([@topic], @context,
                                            @current_user, session).first)
  end

  # @API Get the full topic
  # Return a cached structure of the discussion topic, containing all entries,
  # their authors, and their message bodies.
  #
  # May require (depending on the topic) that the user has posted in the topic.
  # If it is required, and the user has not posted, will respond with a 403
  # Forbidden status and the body 'require_initial_post'.
  #
  # In some rare situations, this cached structure may not be available yet. In
  # that case, the server will respond with a 503 error, and the caller should
  # try again soon.
  #
  # The response is an object containing the following keys:
  # * "participants": A list of summary information on users who have posted to
  #   the discussion. Each value is an object containing their id, display_name,
  #   and avatar_url.
  # * "unread_entries": A list of entry ids that are unread by the current
  #   user. this implies that any entry not in this list is read.
  # * "forced_entries": A list of entry ids that have forced_read_state set to
  #   true. This flag is meant to indicate the entry's read_state has been 
  #   manually set to 'unread' by the user, so the entry should not be
  #   automatically marked as read.
  # * "view": A threaded view of all the entries in the discussion, containing
  #   the id, user_id, and message.
  # * "new_entries": Because this view is eventually consistent, it's possible
  #   that newly created or updated entries won't yet be reflected in the view.
  #   If the application wants to also get a flat list of all entries not yet
  #   reflected in the view, pass include_new_entries=1 to the request and this
  #   array of entries will be returned. These entries are returned in a flat
  #   array, in ascending created_at order.
  #
  # @example_request
  #
  #   curl 'https://<canvas>/api/v1/courses/<course_id>/discussion_topics/<topic_id>/view' \
  #        -H "Authorization: Bearer <token>"
  #
  # @example_response
  #   {
  #     "unread_entries": [1,3,4],
  #     "forced_entries": [1],
  #     "participants": [
  #       { "id": 10, "display_name": "user 1", "avatar_image_url": "https://...", "html_url": "https://..." },
  #       { "id": 11, "display_name": "user 2", "avatar_image_url": "https://...", "html_url": "https://..." }
  #     ],
  #     "view": [
  #       { "id": 1, "user_id": 10, "parent_id": null, "message": "...html text...", "replies": [
  #         { "id": 3, "user_id": 11, "parent_id": 1, "message": "...html....", "replies": [...] }
  #       ]},
  #       { "id": 2, "user_id": 11, "parent_id": null, "message": "...html..." },
  #       { "id": 4, "user_id": 10, "parent_id": null, "message": "...html..." }
  #     ]
  #   }
  def view
    structure, participant_ids, entry_ids, new_entries = @topic.materialized_view(:include_new_entries => params[:include_new_entries] == '1')

    if structure

      # we assume that json_structure will typically be served to users requesting string IDs
      unless stringify_json_ids?
        entries = JSON.parse(structure)
        Api.recursively_stringify_json_ids(entries, reverse: true)
        structure = entries.to_json
      end

      participant_info = Shard.partition_by_shard(participant_ids) do |shard_ids|
        User.find(shard_ids).map do |user|
          user_display_json(user, @context.is_a_context? && @context)
        end
      end
      unread_entries = entry_ids - DiscussionEntryParticipant.read_entry_ids(entry_ids, @current_user)
      unread_entries = unread_entries.map(&:to_s) if stringify_json_ids?
      forced_entries = DiscussionEntryParticipant.forced_read_state_entry_ids(entry_ids, @current_user)
      forced_entries = forced_entries.map(&:to_s) if stringify_json_ids?
      # as an optimization, the view structure is pre-serialized as a json
      # string, so we have to do a bit of manual json building here to fit it
      # into the response.
      fragments = {
        :unread_entries => unread_entries.to_json,
        :forced_entries => forced_entries.to_json,
        :participants   => json_cast(participant_info).to_json,
        :view           => structure,
        :new_entries    => json_cast(new_entries).to_json,<commit_msg>returns shard aware participant ids from discussion view api<commit_after>#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

# @API Discussion Topics
#
# API for accessing and participating in discussion topics in groups and courses.
class DiscussionTopicsApiController < ApplicationController
  include Api::V1::DiscussionTopics
  include Api::V1::User

  before_filter :require_context
  before_filter :require_topic
  before_filter :require_initial_post, except: [:add_entry, :mark_topic_read,
                                                :mark_topic_unread, :show,
                                                :unsubscribe_topic]

  # @API Get a single topic
  #
  # Returns data on an individual discussion topic. See the List action for the response formatting.
  #
  # @example_request
  #
  #     curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics/<topic_id> \ 
  #         -H 'Authorization: Bearer <token>'
  def show
    render(json: discussion_topics_api_json([@topic], @context,
                                            @current_user, session).first)
  end

  # @API Get the full topic
  # Return a cached structure of the discussion topic, containing all entries,
  # their authors, and their message bodies.
  #
  # May require (depending on the topic) that the user has posted in the topic.
  # If it is required, and the user has not posted, will respond with a 403
  # Forbidden status and the body 'require_initial_post'.
  #
  # In some rare situations, this cached structure may not be available yet. In
  # that case, the server will respond with a 503 error, and the caller should
  # try again soon.
  #
  # The response is an object containing the following keys:
  # * "participants": A list of summary information on users who have posted to
  #   the discussion. Each value is an object containing their id, display_name,
  #   and avatar_url.
  # * "unread_entries": A list of entry ids that are unread by the current
  #   user. this implies that any entry not in this list is read.
  # * "forced_entries": A list of entry ids that have forced_read_state set to
  #   true. This flag is meant to indicate the entry's read_state has been 
  #   manually set to 'unread' by the user, so the entry should not be
  #   automatically marked as read.
  # * "view": A threaded view of all the entries in the discussion, containing
  #   the id, user_id, and message.
  # * "new_entries": Because this view is eventually consistent, it's possible
  #   that newly created or updated entries won't yet be reflected in the view.
  #   If the application wants to also get a flat list of all entries not yet
  #   reflected in the view, pass include_new_entries=1 to the request and this
  #   array of entries will be returned. These entries are returned in a flat
  #   array, in ascending created_at order.
  #
  # @example_request
  #
  #   curl 'https://<canvas>/api/v1/courses/<course_id>/discussion_topics/<topic_id>/view' \
  #        -H "Authorization: Bearer <token>"
  #
  # @example_response
  #   {
  #     "unread_entries": [1,3,4],
  #     "forced_entries": [1],
  #     "participants": [
  #       { "id": 10, "display_name": "user 1", "avatar_image_url": "https://...", "html_url": "https://..." },
  #       { "id": 11, "display_name": "user 2", "avatar_image_url": "https://...", "html_url": "https://..." }
  #     ],
  #     "view": [
  #       { "id": 1, "user_id": 10, "parent_id": null, "message": "...html text...", "replies": [
  #         { "id": 3, "user_id": 11, "parent_id": 1, "message": "...html....", "replies": [...] }
  #       ]},
  #       { "id": 2, "user_id": 11, "parent_id": null, "message": "...html..." },
  #       { "id": 4, "user_id": 10, "parent_id": null, "message": "...html..." }
  #     ]
  #   }
  def view
    structure, participant_ids, entry_ids, new_entries = @topic.materialized_view(:include_new_entries => params[:include_new_entries] == '1')

    if structure

      # we assume that json_structure will typically be served to users requesting string IDs
      unless stringify_json_ids?
        entries = JSON.parse(structure)
        Api.recursively_stringify_json_ids(entries, reverse: true)
        structure = entries.to_json
      end

      participants = Shard.partition_by_shard(participant_ids) do |shard_ids|
        User.find(shard_ids)
        end

      participant_info = participants.map do |participant|
        user_display_json(participant, @context.is_a_context? && @context)
      end

      unread_entries = entry_ids - DiscussionEntryParticipant.read_entry_ids(entry_ids, @current_user)
      unread_entries = unread_entries.map(&:to_s) if stringify_json_ids?
      forced_entries = DiscussionEntryParticipant.forced_read_state_entry_ids(entry_ids, @current_user)
      forced_entries = forced_entries.map(&:to_s) if stringify_json_ids?
      # as an optimization, the view structure is pre-serialized as a json
      # string, so we have to do a bit of manual json building here to fit it
      # into the response.
      fragments = {
        :unread_entries => unread_entries.to_json,
        :forced_entries => forced_entries.to_json,
        :participants   => json_cast(participant_info).to_json,
        :view           => structure,
        :new_entries    => json_cast(new_entries).to_json,
{'fraction_non_alphanumeric': 0.10144927536231885, 'fraction_numerical': 0.006780235613187558, 'mean_word_length': 3.0245566166439293, 'pattern_counts': {'":': 70, '<': 23, '<?xml version=': 0, '>': 33, 'https://': 12, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6954822', 'n_tokens_mistral': 3540, 'n_tokens_neox': 3424, 'n_words': 1229}