diff --git a/app/controllers/api/v1/late_policies_controller.rb b/app/controllers/api/v1/late_policies_controller.rb new file mode 100644 index 000000000..c6066962c --- /dev/null +++ b/app/controllers/api/v1/late_policies_controller.rb @@ -0,0 +1,124 @@ +class Api::V1::LatePoliciesController < ApplicationController + include AuthorizationHelper + + before_action :set_late_policy, only: %i[show edit update destroy] + + def action_allowed? + case params[:action] + when 'new', 'create', 'index' + current_user_has_ta_privileges? + when 'edit', 'update', 'destroy' + current_user_has_ta_privileges? && + current_user.instructor_id == check_if_instructor + else + false + end + end + + def index + @penalty_policies = LatePolicy.where(['instructor_id = ? OR private = 0', check_if_instructor]) + render json: @penalty_policies + end + + def show + render json: @penalty_policy + end + + def new + @penalty_policy = LatePolicy.new + render json: @penalty_policy + end + + def edit + render json: @penalty_policy + end + + def create + valid_penalty, error_message = validate_input + if error_message + render json: { error: error_message }, status: :unprocessable_entity and return + end + + if valid_penalty + @late_policy = LatePolicy.new(late_policy_params) + @late_policy.instructor_id = check_if_instructor + if @late_policy.save + render json: @late_policy, status: :created + else + render json: { error: 'The following error occurred while saving the late policy.' }, status: :unprocessable_entity + end + else + render json: { error: 'Validation failed' }, status: :unprocessable_entity + end + end + + def update + valid_penalty, error_message = @penalty_policy.duplicate_name_check(check_if_instructor, params[:id]) + + if error_message + render json: { error: error_message }, status: :unprocessable_entity and return + end + + if valid_penalty + if @penalty_policy.update(late_policy_params) + LatePolicy.update_calculated_penalty_objects(@penalty_policy) + render json: @penalty_policy, status: :ok + else + render json: { error: 'The following error occurred while updating the late policy.' }, status: :unprocessable_entity + end + else + render json: { error: 'Validation failed' }, status: :unprocessable_entity + end + end + + def destroy + if @penalty_policy.destroy + head :no_content + else + render json: { error: 'This policy is in use and hence cannot be deleted.' }, status: :unprocessable_entity + end + end + + private + + def set_late_policy + @penalty_policy = LatePolicy.find(params[:id]) + end + + def late_policy_params + params.require(:late_policy).permit(:policy_name, :penalty_per_unit, :penalty_unit, :max_penalty) + end + + def check_if_instructor + late_policy.try(:instructor_id) || current_user.instructor_id + end + + def late_policy + @penalty_policy ||= @late_policy || LatePolicy.find(params[:id]) if params[:id] + end + + def validate_input(is_update = false) + max_penalty = params[:late_policy][:max_penalty].to_i + penalty_per_unit = params[:late_policy][:penalty_per_unit].to_i + + valid_penalty, error_message = duplicate_name_check(is_update) + prefix = is_update ? "Cannot edit the policy. " : "" + + if max_penalty < penalty_per_unit + error_message = prefix + 'The maximum penalty cannot be less than penalty per unit.' + valid_penalty = false + end + + if penalty_per_unit < 0 + error_message = 'Penalty per unit cannot be negative.' + valid_penalty = false + end + + if max_penalty >= 100 + error_message = prefix + 'Maximum penalty cannot be greater than or equal to 100' + valid_penalty = false + end + + return valid_penalty, error_message + end + end diff --git a/app/controllers/api/v1/versions_controller.rb b/app/controllers/api/v1/versions_controller.rb new file mode 100644 index 000000000..2f515ad9e --- /dev/null +++ b/app/controllers/api/v1/versions_controller.rb @@ -0,0 +1,54 @@ +class Api::V1::VersionsController < ApplicationController + include AuthorizationHelper + + before_action :authorize_admin! + + def index + redirect_to action: :search + end + + def show + @version = Version.find_by(id: params[:id]) + if @version + render json: @version + else + render json: { error: 'Version not found' }, status: :not_found + end + end + + def search + @per_page = (params[:num_versions] || 25).to_i + + @versions = if params[:post] + paginate_list + else + Version.page(params[:page]).order('id').per(@per_page) + end + + render json: @versions + end + + private + + def authorize_admin! + render json: { error: 'Forbidden' }, status: :forbidden unless current_user_has_admin_privileges? + end + + # For filtering the versions list with proper search and pagination. + def paginate_list + versions = Version.page(params[:page]).order('id').per(@per_page) + versions = versions.where(id: params[:id]) if params[:id].to_i > 0 + if current_user_has_super_admin_privileges? + versions = versions.where(whodunnit: params[:post][:user_id]) if params[:post][:user_id].to_i > 0 + end + versions = versions.where(whodunnit: current_user.try(:id)) if current_user.try(:id).to_i > 0 + versions = versions.where(item_type: params[:post][:item_type]) if params[:post][:item_type] && params[:post][:item_type] != 'Any' + versions = versions.where(event: params[:post][:event]) if params[:post][:event] && params[:post][:event] != 'Any' + versions = versions.where('created_at >= ? and created_at <= ?', time_to_string(params[:start_time]), time_to_string(params[:end_time])) if params[:start_time] && params[:end_time] + versions + end + + def time_to_string(time) + "#{time.tr('/', '-')}:00" + end + end \ No newline at end of file diff --git a/app/models/late_policy.rb b/app/models/late_policy.rb new file mode 100644 index 000000000..9f86790e9 --- /dev/null +++ b/app/models/late_policy.rb @@ -0,0 +1,41 @@ +class LatePolicy < ApplicationRecord + belongs_to :user + + has_many :assignments, dependent: :nullify + + validates :policy_name, presence: true, format: { with: /\A[A-Za-z0-9][A-Za-z0-9\s'._-]+\z/i } + validates :instructor_id, presence: true + validates :max_penalty, presence: true, numericality: { greater_than: 0, less than: 100 } + validates :penalty_per_unit, presence: true, numericality: { greater_than: 0 } + validates :penalty_unit, presence: true + + def duplicate_name_check(instructor_id, current_policy_id = nil) + existing_policy = LatePolicy.find_by(policy_name: self.policy_name, instructor_id: instructor_id) + + if existing_policy && (current_policy_id.nil? || existing_policy.id != current_policy_id) + error_message = "A policy with the same name #{self.policy_name} already exists." + return false, error_message + end + + return true, nil + end + + def self.update_calculated_penalty_objects(late_policy) + CalculatedPenalty.find_each do |pen| + participant = AssignmentParticipant.find(pen.participant_id) + assignment = participant.assignment + next unless assignment.late_policy_id == late_policy.id + + penalties = calculate_penalty(pen.participant_id) + total_penalty = penalties.values.sum + case pen.deadline_type_id.to_i + when 1 + pen.update(penalty_points: penalties[:submission]) + when 2 + pen.update(penalty_points: penalties[:review]) + when 5 + pen.update(penalty_points: penalties[:meta_review]) + end + end + end +end