<commit_before>require 'grape'
require 'unit_serializer'
require 'mime-check-helpers'

module Api
  class Units < Grape::API
    helpers AuthenticationHelpers
    helpers AuthorisationHelpers
    helpers MimeCheckHelpers

    before do
      authenticated?

      if params[:unit]
        for key in [ :start_date, :end_date ] do
          if params[:unit][key].present?
            date_val = DateTime.parse(params[:unit][key])
            params[:unit][key] = date_val
          end
        end
      end
    end

    desc 'Get units related to the current user for admin purposes'
    params do
      optional :include_in_active, type: Boolean, desc: 'Include units that are not active'
    end
    get '/units' do
      unless authorise? current_user, User, :convene_units
        error!({ error: 'Unable to list units' }, 403)
      end

      # gets only the units the current user can "see"
      units = Unit.for_user_admin current_user

      units = units.where('active = true') unless params[:include_in_active]

      ActiveModel::ArraySerializer.new(units, each_serializer: ShallowUnitSerializer)
    end

    desc "Get a unit's details"
    get '/units/:id' do
      unit = Unit.find(params[:id])
      unless (authorise? current_user, unit, :get_unit) || (authorise? current_user, User, :admin_units)
        error!({ error: "Couldn't find Unit with id=#{params[:id]}" }, 403)
      end
      #
      # Unit uses user from thread to limit exposure
      #
      Thread.current[:user] = current_user
      unit
    end

    desc 'Update unit'
    params do
      requires :id, type: Integer, desc: 'The unit id to update'
      requires :unit, type: Hash do
        optional :name
        optional :code
        optional :description
        optional :start_date
        optional :end_date
        optional :active
      end
    end
    put '/units/:id' do
      unit = Unit.find(params[:id])
      unless authorise? current_user, unit, :update
        error!({ error: 'Not authorised to update a unit' }, 403)
      end
      unit_parameters = ActionController::Parameters.new(params)
                                                    .require(:unit)
                                                    .permit(:name,
                                                            :code,
                                                            :description,
                                                            :start_date,
                                                            :end_date,
                                                            :active)

      unit.update!(unit_parameters)
      unit_parameters
    end

    desc 'Create unit'
    params do
      requires :unit, type: Hash do
        requires :name
        requires :code
        optional :description
        optional :start_date
        optional :end_date
      end
    end
    post '/units' do
      unless authorise? current_user, User, :create_unit
        error!({ error: 'Not authorised to create a unit' }, 403)
      end

      unit_parameters = ActionController::Parameters.new(params)
                                                    .require(:unit)
                                                    .permit(
                                                      :name,
                                                      :code,
                                                      :description,
                                                      :start_date,
                                                      :end_date
                                                    )

      if unit_parameters[:description].nil?
        unit_parameters[:description] = unit_parameters[:name]
      end
      if unit_parameters[:start_date].nil?
        start_date = Date.parse('Monday')
        delta = start_date > Date.today ? 0 : 7
        unit_parameters[:start_date] = start_date + delta
      end
      if unit_parameters[:end_date].nil?
        unit_parameters[:end_date] = unit_parameters[:start_date] + 16.weeks
      end

      unit = Unit.create!(unit_parameters)

      # Employ current user as convenor
      unit.employ_staff(current_user, Role.convenor)
      ShallowUnitSerializer.new(unit)
    end

    desc 'Add a tutorial with the provided details to this unit'
    params do
      # day, time, location, tutor_username, abbrev
      requires :tutorial, type: Hash do
        requires :day
        requires :time
        requires :location
        requires :tutor_username
        requires :abbrev
      end
    end
    post '/units/:id/tutorials' do
      unit = Unit.find(params[:id])
      unless authorise? current_user, unit, :add_tutorial
        error!({ error: 'Not authorised to create a tutorial' }, 403)
      end

      new_tutorial = params[:tutorial]
      tutor = User.find_by(username: new_tutorial[:tutor_username])
      if tutor.nil?
        error!({ error: "Couldn't find User with username=#{new_tutorial[:tutor_username]}" }, 403)
      end

      result = unit.add_tutorial(new_tutorial[:day], new_tutorial[:time], new_tutorial[:location], tutor, new_tutorial[:abbrev])
      if result.nil?
        error!({ error: 'Tutor username invalid (not a tutor for this unit)' }, 403)
      end

      result
    end

    desc 'Download the tasks that are awaiting feedback for a unit'
    get '/units/:id/feedback' do
      unit = Unit.find(params[:id])

      unless authorise? current_user, unit, :provide_feedback
        error!({ error: 'Not authorised to provide feedback for this unit' }, 403)
      end

      unit.tasks_awaiting_feedback.map do |t|
        {
          id: t.id,
          project_id: t.project_id,
          task_definition_id: t.task_definition_id,
          tutorial_id: t.tutorial_id,
          status: TaskStatus.status_key_for_name(t.status_name),
          completion_date: t.completion_date,
          submission_date: t.submission_date,
          times_assessed: t.times_assessed,
          grade: t.grade,
          quality_pts: t.quality_pts,
          num_new_comments: t.number_of_comments_unread_for(current_user)
        }
      end
    end

    desc 'Download the tasks that should be listed under the task inbox'
    get '/units/:id/tasks/inbox' do
      unit = Unit.find(params[:id])

      unless authorise? current_user, unit, :provide_feedback
        error!({ error: 'Not authorised to provide feedback for this unit' }, 403)
      end

      tasks = unit.tasks_for_task_inbox(current_user)
      tasks.sort_by { |task| [task.comments.pluck(:created_at).max, task.submission_date] }

      tasks.map do |t|
        {
          id: t.id,
          project_id: t.project_id,
          task_definition_id: t.task_definition_id,
          tutorial_id: t.project.tutorial_id,
          status: t.project.status,
          completion_date: t.completion_date,
          submission_date: t.submission_date,
          times_assessed: t.times_assessed,
          grade: t.grade,
          quality_pts: t.quality_pts,<commit_msg>QUALITY: Use action_date for sorting inbox tasks<commit_after>require 'grape'
require 'unit_serializer'
require 'mime-check-helpers'

module Api
  class Units < Grape::API
    helpers AuthenticationHelpers
    helpers AuthorisationHelpers
    helpers MimeCheckHelpers

    before do
      authenticated?

      if params[:unit]
        for key in [ :start_date, :end_date ] do
          if params[:unit][key].present?
            date_val = DateTime.parse(params[:unit][key])
            params[:unit][key] = date_val
          end
        end
      end
    end

    desc 'Get units related to the current user for admin purposes'
    params do
      optional :include_in_active, type: Boolean, desc: 'Include units that are not active'
    end
    get '/units' do
      unless authorise? current_user, User, :convene_units
        error!({ error: 'Unable to list units' }, 403)
      end

      # gets only the units the current user can "see"
      units = Unit.for_user_admin current_user

      units = units.where('active = true') unless params[:include_in_active]

      ActiveModel::ArraySerializer.new(units, each_serializer: ShallowUnitSerializer)
    end

    desc "Get a unit's details"
    get '/units/:id' do
      unit = Unit.find(params[:id])
      unless (authorise? current_user, unit, :get_unit) || (authorise? current_user, User, :admin_units)
        error!({ error: "Couldn't find Unit with id=#{params[:id]}" }, 403)
      end
      #
      # Unit uses user from thread to limit exposure
      #
      Thread.current[:user] = current_user
      unit
    end

    desc 'Update unit'
    params do
      requires :id, type: Integer, desc: 'The unit id to update'
      requires :unit, type: Hash do
        optional :name
        optional :code
        optional :description
        optional :start_date
        optional :end_date
        optional :active
      end
    end
    put '/units/:id' do
      unit = Unit.find(params[:id])
      unless authorise? current_user, unit, :update
        error!({ error: 'Not authorised to update a unit' }, 403)
      end
      unit_parameters = ActionController::Parameters.new(params)
                                                    .require(:unit)
                                                    .permit(:name,
                                                            :code,
                                                            :description,
                                                            :start_date,
                                                            :end_date,
                                                            :active)

      unit.update!(unit_parameters)
      unit_parameters
    end

    desc 'Create unit'
    params do
      requires :unit, type: Hash do
        requires :name
        requires :code
        optional :description
        optional :start_date
        optional :end_date
      end
    end
    post '/units' do
      unless authorise? current_user, User, :create_unit
        error!({ error: 'Not authorised to create a unit' }, 403)
      end

      unit_parameters = ActionController::Parameters.new(params)
                                                    .require(:unit)
                                                    .permit(
                                                      :name,
                                                      :code,
                                                      :description,
                                                      :start_date,
                                                      :end_date
                                                    )

      if unit_parameters[:description].nil?
        unit_parameters[:description] = unit_parameters[:name]
      end
      if unit_parameters[:start_date].nil?
        start_date = Date.parse('Monday')
        delta = start_date > Date.today ? 0 : 7
        unit_parameters[:start_date] = start_date + delta
      end
      if unit_parameters[:end_date].nil?
        unit_parameters[:end_date] = unit_parameters[:start_date] + 16.weeks
      end

      unit = Unit.create!(unit_parameters)

      # Employ current user as convenor
      unit.employ_staff(current_user, Role.convenor)
      ShallowUnitSerializer.new(unit)
    end

    desc 'Add a tutorial with the provided details to this unit'
    params do
      # day, time, location, tutor_username, abbrev
      requires :tutorial, type: Hash do
        requires :day
        requires :time
        requires :location
        requires :tutor_username
        requires :abbrev
      end
    end
    post '/units/:id/tutorials' do
      unit = Unit.find(params[:id])
      unless authorise? current_user, unit, :add_tutorial
        error!({ error: 'Not authorised to create a tutorial' }, 403)
      end

      new_tutorial = params[:tutorial]
      tutor = User.find_by(username: new_tutorial[:tutor_username])
      if tutor.nil?
        error!({ error: "Couldn't find User with username=#{new_tutorial[:tutor_username]}" }, 403)
      end

      result = unit.add_tutorial(new_tutorial[:day], new_tutorial[:time], new_tutorial[:location], tutor, new_tutorial[:abbrev])
      if result.nil?
        error!({ error: 'Tutor username invalid (not a tutor for this unit)' }, 403)
      end

      result
    end

    desc 'Download the tasks that are awaiting feedback for a unit'
    get '/units/:id/feedback' do
      unit = Unit.find(params[:id])

      unless authorise? current_user, unit, :provide_feedback
        error!({ error: 'Not authorised to provide feedback for this unit' }, 403)
      end

      unit.tasks_awaiting_feedback.map do |t|
        {
          id: t.id,
          project_id: t.project_id,
          task_definition_id: t.task_definition_id,
          tutorial_id: t.tutorial_id,
          status: TaskStatus.status_key_for_name(t.status_name),
          completion_date: t.completion_date,
          submission_date: t.submission_date,
          times_assessed: t.times_assessed,
          grade: t.grade,
          quality_pts: t.quality_pts,
          num_new_comments: t.number_of_comments_unread_for(current_user)
        }
      end
    end

    desc 'Download the tasks that should be listed under the task inbox'
    get '/units/:id/tasks/inbox' do
      unit = Unit.find(params[:id])

      unless authorise? current_user, unit, :provide_feedback
        error!({ error: 'Not authorised to provide feedback for this unit' }, 403)
      end

      tasks = unit.tasks_for_task_inbox(current_user)
      tasks.sort_by { |task| [task.action_date ? 1 : 0, task.action_date] }

      tasks.map do |t|
        {
          id: t.id,
          project_id: t.project_id,
          task_definition_id: t.task_definition_id,
          tutorial_id: t.project.tutorial_id,
          status: t.project.status,
          completion_date: t.completion_date,
          submission_date: t.submission_date,
          times_assessed: t.times_assessed,
          grade: t.grade,
          quality_pts: t.quality_pts,
{'fraction_non_alphanumeric': 0.0832443970117396, 'fraction_numerical': 0.004553539665599431, 'mean_word_length': 1.7319727891156462, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 64, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '299083', 'n_tokens_mistral': 3834, 'n_tokens_neox': 3648, 'n_words': 1021}