diff --git a/app/controllers/api/v1/assignments_controller.rb b/app/controllers/api/v1/assignments_controller.rb index e28ad573f..9d5d16081 100644 --- a/app/controllers/api/v1/assignments_controller.rb +++ b/app/controllers/api/v1/assignments_controller.rb @@ -145,8 +145,9 @@ def show_assignment_details end end - # check if assignment has topics - # has_topics is set to true if there is SignUpTopic corresponding to the input assignment id + # Checks if an assignment has associated topics. + # If the assignment is found, it returns a boolean indicating whether topics exist for it. + # If the assignment is not found, it returns an error message. def has_topics assignment = Assignment.find_by(id: params[:assignment_id]) if assignment.nil? diff --git a/app/controllers/api/v1/project_topics_controller.rb b/app/controllers/api/v1/project_topics_controller.rb new file mode 100644 index 000000000..23cfc3c23 --- /dev/null +++ b/app/controllers/api/v1/project_topics_controller.rb @@ -0,0 +1,87 @@ +class Api::V1::ProjectTopicsController < ApplicationController + before_action :set_project_topic, only: %i[ show update ] + + # GET /api/v1/project_topics?assignment_id=&topic_ids[]= + # Retrieve ProjectTopics by two query parameters - assignment_id (compulsory) and an array of topic_ids (optional) + def index + if params[:assignment_id].nil? + render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity + elsif params[:topic_ids].nil? + @project_topics = ProjectTopic.where(assignment_id: params[:assignment_id]) + render json: @project_topics, status: :ok + else + @project_topics = ProjectTopic.where(assignment_id: params[:assignment_id], topic_identifier: params[:topic_ids]) + render json: @project_topics, status: :ok + end + end + + # POST /project_topics + # The create method allows the instructor to create a new topic + # params[:project_topic][:topic_identifier] follows a json format + # The method takes inputs and outputs the if the topic creation was successful. + def create + @project_topic = ProjectTopic.new(project_topic_params) + @assignment = Assignment.find(params[:project_topic][:assignment_id]) + @project_topic.micropayment = params[:micropayment] if @assignment.microtask? + if @project_topic.save + render json: { message: "The topic: \"#{@project_topic.topic_name}\" has been created successfully. " }, status: :created + else + render json: { message: @project_topic.errors }, status: :unprocessable_entity + end + end + + # PATCH/PUT /project_topics/1 + # updates parameters present in project_topic_params. + def update + if @project_topic.update(project_topic_params) + render json: { message: "The topic: \"#{@project_topic.topic_name}\" has been updated successfully. " }, status: 200 + else + render json: @project_topic.errors, status: :unprocessable_entity + end + end + + # GET /project_topics/:id + # Show a ProjectTopic by ID + def show + render json: @project_topic, status: :ok + end + +# DELETE /project_topics +# Deletes one or more project topics associated with an assignment. +# If `assignment_id` or `topic_ids` are missing, appropriate validations are handled. +# Deletes the topics and returns a success message if successful or error messages otherwise. + def destroy + # Check if the assignment ID is provided + if params[:assignment_id].nil? + render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity + # Determine which topics to delete based on the provided parameters + elsif params[:topic_ids].nil? + # If no specific topic IDs are provided, fetch all topics for the assignment + @project_topics = ProjectTopic.where(assignment_id: params[:assignment_id]) + else + # Fetch the specified topics for the assignment + @project_topics = ProjectTopic.where(assignment_id: params[:assignment_id], topic_identifier: params[:topic_ids]) + end + + # Attempt to delete the topics and return the appropriate response + if @project_topics.each(&:delete) + render json: { message: "The topic has been deleted successfully. " }, status: :no_content + else + render json: @project_topic.errors, status: :unprocessable_entity + end + end + + private + + # Callback to set the @project_topic instance variable. + # This method is executed before certain actions (via `before_action`) to load the project topic. + # If the topic is not found, it raises an ActiveRecord::RecordNotFound exception. + def set_project_topic + @project_topic = ProjectTopic.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def project_topic_params + params.require(:project_topic).permit(:topic_identifier, :category, :topic_name, :max_choosers, :assignment_id) + end +end diff --git a/app/controllers/api/v1/sign_up_topics_controller.rb b/app/controllers/api/v1/sign_up_topics_controller.rb deleted file mode 100644 index a736f3eaa..000000000 --- a/app/controllers/api/v1/sign_up_topics_controller.rb +++ /dev/null @@ -1,84 +0,0 @@ -class Api::V1::SignUpTopicsController < ApplicationController - before_action :set_sign_up_topic, only: %i[ show update ] - - # GET /api/v1/sign_up_topics?assignment_id=&topic_ids[]= - # Retrieve SignUpTopics by two query parameters - assignment_id (compulsory) and an array of topic_ids (optional) - def index - if params[:assignment_id].nil? - render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity - elsif params[:topic_ids].nil? - @sign_up_topics = SignUpTopic.where(assignment_id: params[:assignment_id]) - render json: @sign_up_topics, status: :ok - else - @sign_up_topics = SignUpTopic.where(assignment_id: params[:assignment_id], topic_identifier: params[:topic_ids]) - render json: @sign_up_topics, status: :ok - end - # render json: {message: 'All selected topics have been loaded successfully.', sign_up_topics: @stopics}, status: 200 - end - - # POST /sign_up_topics - # The create method allows the instructor to create a new topic - # params[:sign_up_topic][:topic_identifier] follows a json format - # The method takes inputs and outputs the if the topic creation was successful. - def create - @sign_up_topic = SignUpTopic.new(sign_up_topic_params) - @assignment = Assignment.find(params[:sign_up_topic][:assignment_id]) - @sign_up_topic.micropayment = params[:micropayment] if @assignment.microtask? - if @sign_up_topic.save - # undo_link "The topic: \"#{@sign_up_topic.topic_name}\" has been created successfully. " - render json: { message: "The topic: \"#{@sign_up_topic.topic_name}\" has been created successfully. " }, status: :created - else - render json: { message: @sign_up_topic.errors }, status: :unprocessable_entity - end - end - - # PATCH/PUT /sign_up_topics/1 - # updates parameters present in sign_up_topic_params. - def update - if @sign_up_topic.update(sign_up_topic_params) - render json: { message: "The topic: \"#{@sign_up_topic.topic_name}\" has been updated successfully. " }, status: 200 - else - render json: @sign_up_topic.errors, status: :unprocessable_entity - end - end - - # Show a SignUpTopic by ID - def show - render json: @sign_up_topic, status: :ok - end - - # Similar to index method, this method destroys SignUpTopics by two query parameters - # assignment_id is compulsory. - # topic_ids[] is optional - def destroy - # render json: {message: @sign_up_topic} - # filters topics based on assignment id (required) and topic identifiers (optional) - if params[:assignment_id].nil? - render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity - elsif params[:topic_ids].nil? - @sign_up_topics = SignUpTopic.where(assignment_id: params[:assignment_id]) - # render json: @sign_up_topics, status: :ok - else - @sign_up_topics = SignUpTopic.where(assignment_id: params[:assignment_id], topic_identifier: params[:topic_ids]) - # render json: @sign_up_topics, status: :ok - end - - if @sign_up_topics.each(&:delete) - render json: { message: "The topic has been deleted successfully. " }, status: :no_content - else - render json: @sign_up_topic.errors, status: :unprocessable_entity - end - end - - private - - # Use callbacks to share common setup or constraints between actions. - def set_sign_up_topic - @sign_up_topic = SignUpTopic.find(params[:id]) - end - - # Only allow a list of trusted parameters through. - def sign_up_topic_params - params.require(:sign_up_topic).permit(:topic_identifier, :category, :topic_name, :max_choosers, :assignment_id) - end -end diff --git a/app/controllers/api/v1/signed_up_teams_controller.rb b/app/controllers/api/v1/signed_up_teams_controller.rb index 97ada5a24..801fc6fc0 100644 --- a/app/controllers/api/v1/signed_up_teams_controller.rb +++ b/app/controllers/api/v1/signed_up_teams_controller.rb @@ -1,15 +1,14 @@ class Api::V1::SignedUpTeamsController < ApplicationController - # Returns signed up topics using sign_up_topic assignment id - # Retrieves sign_up_topic using topic_id as a parameter + # Returns signed up topics using project_topic assignment id + # Retrieves project_topic using topic_id as a parameter def index - # puts params[:topic_id] - @sign_up_topic = SignUpTopic.find(params[:topic_id]) - @signed_up_team = SignedUpTeam.find_team_participants(@sign_up_topic.assignment_id) + @project_topic = ProjectTopic.find(params[:topic_id]) + @signed_up_team = SignedUpTeam.find_team_participants(@project_topic.assignment_id) render json: @signed_up_team end - # Implemented by signed_up_team.rb (Model) --> create_signed_up_team + # Implemented by signed_up_team.rb (Model) --> signup_team_for_topic def create; end # Update signed_up_team using parameters. @@ -23,11 +22,12 @@ def update end # Sign up using parameters: team_id and topic_id - # Calls model method create_signed_up_team - def sign_up + # Calls model method signup_team_for_topic + def signup team_id = params[:team_id] topic_id = params[:topic_id] - @signed_up_team = SignedUpTeam.create_signed_up_team(topic_id, team_id) + @signed_up_team = SignedUpTeam.signup_team_for_topic(topic_id, team_id) + @signed_up_team.save if @signed_up_team render json: { message: "Signed up team successful!" }, status: :created else @@ -37,16 +37,14 @@ def sign_up # Method for signing up as student # Params : topic_id - # Get team_id using model method get_team_participants - # Call create_signed_up_team Model method - def sign_up_student + # Get team_id using model method get_team_id_for_user + # Call signup_team_for_topic Model method + def signup_user user_id = params[:user_id] topic_id = params[:topic_id] - team_id = SignedUpTeam.get_team_participants(user_id) - # @teams_user = TeamsUser.where(user_id: user_id).first - # team_id = @teams_user.team_id - @signed_up_team = SignedUpTeam.create_signed_up_team(topic_id, team_id) - # create(topic_id, team_id) + assignment_id = params[:assignment_id] + team_id = SignedUpTeam.get_team_id_for_user(user_id, assignment_id) + @signed_up_team = SignedUpTeam.signup_team_for_topic(topic_id, team_id) if @signed_up_team render json: { message: "Signed up team successful!" }, status: :created else @@ -66,6 +64,8 @@ def destroy private + # Strong parameters method for permitting attributes related to signed-up teams. + # Ensures only the allowed parameters are passed for creating or updating a signed-up team. def signed_up_teams_params params.require(:signed_up_team).permit(:topic_id, :team_id, :is_waitlisted, :preference_priority_number) end diff --git a/app/models/assignment.rb b/app/models/assignment.rb index 45e8d2acf..7ee426157 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -8,7 +8,7 @@ class Assignment < ApplicationRecord has_many :questionnaires, through: :assignment_questionnaires has_many :response_maps, foreign_key: 'reviewed_object_id', dependent: :destroy, inverse_of: :assignment has_many :review_mappings, class_name: 'ReviewResponseMap', foreign_key: 'reviewed_object_id', dependent: :destroy, inverse_of: :assignment - has_many :sign_up_topics , class_name: 'SignUpTopic', foreign_key: 'assignment_id', dependent: :destroy + has_many :project_topics , class_name: 'ProjectTopic', foreign_key: 'assignment_id', dependent: :destroy belongs_to :course, optional: true belongs_to :instructor, class_name: 'User', inverse_of: :assignments @@ -137,7 +137,7 @@ def staggered_and_no_topic?(topic_id) #This method return the value of the has_topics field for the given assignment object. # has_topics is of boolean type and is set true if there is any topic associated with the assignment. def topics? - @has_topics ||= sign_up_topics.any? + @has_topics ||= project_topics.any? end #This method return if the given assignment is a team assignment. diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb index 2f02eabb5..df2fddb39 100644 --- a/app/models/bookmark.rb +++ b/app/models/bookmark.rb @@ -1,6 +1,6 @@ class Bookmark < ApplicationRecord belongs_to :user - # belongs_to :topic, class_name: "SignUpTopic" + # belongs_to :topic, class_name: "ProjectTopic" has_many :bookmark_ratings validates :url, presence: true validates :title, presence: true diff --git a/app/models/project_topic.rb b/app/models/project_topic.rb new file mode 100644 index 000000000..b3fceff8d --- /dev/null +++ b/app/models/project_topic.rb @@ -0,0 +1,82 @@ +class ProjectTopic < ApplicationRecord + has_many :signed_up_teams, foreign_key: :sign_up_topic_id, dependent: :destroy + has_many :teams, through: :signed_up_teams # list all teams choose this topic, no matter in waitlist or not + has_many :assignment_questionnaires, class_name: 'AssignmentQuestionnaire', foreign_key: 'topic_id', dependent: :destroy + belongs_to :assignment + + # max_choosers should be a non-negative integer + validates :max_choosers, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + + # Checks if there are any slots currently available + # Returns true if the number of available slots is greater than 0, otherwise false + def slot_available? + current_available_slots > 0 + end + + # Signs up a team for the current topic. + # Checks if the team is already signed up, and if so, ensures they are not waitlisted. + # If a slot is available, assigns the topic to the team; otherwise, adds the team to the waitlist. + def signup_team(team_id) + # Check if the team has already signed up for this topic + team_signup_record = SignedUpTeam.find_by(sign_up_topic_id: self.id, team_id: team_id, is_waitlisted: false) + + # If the team is already signed up, return false + if !team_signup_record.nil? + return false + end + + # Create a new sign-up entry for the team + new_signup_record = SignedUpTeam.new(sign_up_topic_id: self.id, team_id: team_id) + + # If there are available slots, assign the topic to the team and remove the team from the waitlist + if slot_available? + new_signup_record.update(is_waitlisted: false, sign_up_topic_id: self.id) + result = SignedUpTeam.drop_off_topic_waitlists(team_id) + else + # If no slots are available, add the team to the waitlist + result = new_signup_record.update(is_waitlisted: true, sign_up_topic_id: self.id) + end + + result + end + + # Retrieves the team with the earliest waitlisted record for a given topic. + # The team is determined based on the creation time of the waitlisted record. + def longest_waiting_team + SignedUpTeam.where(sign_up_topic_id: self.id, is_waitlisted: true).order(:created_at).first + end + + # Removes a team from the current topic. + # If the team is not waitlisted, the next waitlisted team is reassigned to the topic. + # The team is then destroyed (removed from the sign-up record). + def drop_team_from_topic(team_id) + # Find the sign-up record for the team for this topic + signed_up_team = SignedUpTeam.find_by(team_id: team_id, sign_up_topic_id: self.id) + return nil unless signed_up_team + + # If the team is not waitlisted, reassign the topic to the next waitlisted team + unless signed_up_team.is_waitlisted + next_waitlisted_team = longest_waiting_team + next_waitlisted_team&.assign_topic_to_waitlisted_team(self.id) + end + + # Destroy the sign-up record for the team + signed_up_team.destroy + end + + # Retrieves all teams that are signed up for a given topic. + def signed_up_teams_for_topic + SignedUpTeam.where(sign_up_topic_id: self.id) + end + + # Calculates the number of available slots for a topic. + # It checks how many teams have already chosen the topic and subtracts that from the maximum allowed choosers. + def current_available_slots + # Find the number of teams who have already chosen the topic and are not waitlisted + # This would give us the number of teams who have been assigned the topic + num_teams_registered = SignedUpTeam.where(sign_up_topic_id: self.id, is_waitlisted: false).size + + # Compute the number of available slots and return + self.max_choosers.to_i - num_teams_registered + end +end diff --git a/app/models/sign_up_topic.rb b/app/models/sign_up_topic.rb deleted file mode 100644 index 366dc9f97..000000000 --- a/app/models/sign_up_topic.rb +++ /dev/null @@ -1,6 +0,0 @@ -class SignUpTopic < ApplicationRecord - has_many :signed_up_teams, foreign_key: 'topic_id', dependent: :destroy - has_many :teams, through: :signed_up_teams # list all teams choose this topic, no matter in waitlist or not - has_many :assignment_questionnaires, class_name: 'AssignmentQuestionnaire', foreign_key: 'topic_id', dependent: :destroy - belongs_to :assignment -end diff --git a/app/models/signed_up_team.rb b/app/models/signed_up_team.rb index 39b1e2de4..4e6abcef3 100644 --- a/app/models/signed_up_team.rb +++ b/app/models/signed_up_team.rb @@ -1,4 +1,60 @@ class SignedUpTeam < ApplicationRecord - belongs_to :sign_up_topic + belongs_to :project_topic, foreign_key: :sign_up_topic_id belongs_to :team + + # Removes all waitlist entries for a given team. + # This deletes all sign-up records where the team is waitlisted. + def self.drop_off_topic_waitlists(team_id) + SignedUpTeam.where(team_id: team_id, is_waitlisted: true).destroy_all + end + + # Signs up a team for a specific project topic. + # Finds the project topic by its ID and associates the given team with the topic by calling `signup_team`. + def self.signup_team_for_topic(topic_id, team_id) + # Find the project topic by its ID + project_topic = ProjectTopic.find(topic_id) + + # Sign up the team for the topic + project_topic.signup_team(team_id) + end + + # Retrieves the team ID for a given user and assignment. + # This first finds the team(s) the user is associated with and then retrieves the team for the specified assignment. + # NOTE: This method is called in signed_up_teams_controller + def self.get_team_id_for_user(user_id, assignment_id) + # Get the team IDs associated with the given user + team_ids = TeamsUser.select('team_id').where(user_id: user_id) + + if team_ids.empty? + return nil + end + # Find the team that matches the assignment ID and retrieve its team_id + team_id = Team.where(id: team_ids, assignment_id: assignment_id).first.id + # Return the team ID + team_id + end + + # Deletes all sign-up records for a given team. + # This removes all sign-up entries associated with the specified team. + def self.delete_team_signup_records(team_id) + SignedUpTeam.where(team_id: team_id).destroy_all + end + + # Reassigns the team to a new topic by removing them from their current topic + # and marking them as no longer waitlisted for the new topic. + # NOTE: This method gets called only on a waitlisted team (See project_topic.rb -> drop_team_from_topic) + def assign_topic_to_waitlisted_team(topic_id) + # Find the team's current sign-up record where they are not waitlisted + # As this method gets called only on a waitlisted team, we need to check if the team has been assigned another topic + assigned_team = SignedUpTeam.find_by(team_id: self.team_id, is_waitlisted: false) + + # If the team is already assigned to a topic, remove them from that topic + if assigned_team + project_topic = ProjectTopic.find(assigned_team.sign_up_topic_id) + project_topic.drop_team_from_topic(team_id: self.team_id) + end + + # Update the team's waitlist status to false (indicating they are no longer waitlisted) + self.update(sign_up_topic_id: topic_id, is_waitlisted: false) + end end diff --git a/config/routes.rb b/config/routes.rb index fc8a710a2..7a54c2aea 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,8 +79,8 @@ resources :signed_up_teams do collection do - post '/sign_up', to: 'signed_up_teams#sign_up' - post '/sign_up_student', to: 'signed_up_teams#sign_up_student' + post '/signup', to: 'signed_up_teams#signup' + post '/signup_user', to: 'signed_up_teams#signup_user' end end @@ -92,10 +92,10 @@ - resources :sign_up_topics do + resources :project_topics do collection do get :filter - delete '/', to: 'sign_up_topics#destroy' + delete '/', to: 'project_topics#destroy' end end diff --git a/db/migrate/20241026015522_rename_sign_up_topics_to_project_topics.rb b/db/migrate/20241026015522_rename_sign_up_topics_to_project_topics.rb new file mode 100644 index 000000000..245e8a242 --- /dev/null +++ b/db/migrate/20241026015522_rename_sign_up_topics_to_project_topics.rb @@ -0,0 +1,5 @@ +class RenameSignUpTopicsToProjectTopics < ActiveRecord::Migration[7.0] + def change + rename_table :sign_up_topics, :project_topics + end +end diff --git a/db/schema.rb b/db/schema.rb index 1770d8997..3fa6347c8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_04_15_192048) do +ActiveRecord::Schema[7.0].define(version: 2024_10_26_015522) do create_table "account_requests", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "username" t.string "full_name" @@ -194,6 +194,22 @@ t.index ["user_id"], name: "index_participants_on_user_id" end + create_table "project_topics", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.text "topic_name", null: false + t.bigint "assignment_id", null: false + t.integer "max_choosers", default: 0, null: false + t.text "category" + t.string "topic_identifier", limit: 10 + t.integer "micropayment", default: 0 + t.integer "private_to" + t.text "description" + t.string "link" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["assignment_id"], name: "fk_sign_up_categories_sign_up_topics" + t.index ["assignment_id"], name: "index_project_topics_on_assignment_id" + end + create_table "questionnaires", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name" t.integer "instructor_id" @@ -251,22 +267,6 @@ t.index ["parent_id"], name: "fk_rails_4404228d2f" end - create_table "sign_up_topics", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| - t.text "topic_name", null: false - t.bigint "assignment_id", null: false - t.integer "max_choosers", default: 0, null: false - t.text "category" - t.string "topic_identifier", limit: 10 - t.integer "micropayment", default: 0 - t.integer "private_to" - t.text "description" - t.string "link" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["assignment_id"], name: "fk_sign_up_categories_sign_up_topics" - t.index ["assignment_id"], name: "index_sign_up_topics_on_assignment_id" - end - create_table "signed_up_teams", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "sign_up_topic_id", null: false t.bigint "team_id", null: false @@ -341,10 +341,10 @@ add_foreign_key "participants", "join_team_requests" add_foreign_key "participants", "teams" add_foreign_key "participants", "users" + add_foreign_key "project_topics", "assignments" add_foreign_key "questions", "questionnaires" add_foreign_key "roles", "roles", column: "parent_id", on_delete: :cascade - add_foreign_key "sign_up_topics", "assignments" - add_foreign_key "signed_up_teams", "sign_up_topics" + add_foreign_key "signed_up_teams", "project_topics", column: "sign_up_topic_id" add_foreign_key "signed_up_teams", "teams" add_foreign_key "ta_mappings", "courses" add_foreign_key "ta_mappings", "users" diff --git a/spec/models/project_topic_spec.rb b/spec/models/project_topic_spec.rb new file mode 100644 index 000000000..792334568 --- /dev/null +++ b/spec/models/project_topic_spec.rb @@ -0,0 +1,119 @@ +require 'rails_helper' + +RSpec.describe ProjectTopic, type: :model do + # Set up initial data for tests + let!(:role) { Role.create!(name: "Professor") } + let!(:instructor) { Instructor.create!(name: "Ed", password: "sec-Key1", full_name: "Ed Gehringer", email: "efg@ncsu.edu", role_id: role.id) } + let!(:assignment) { Assignment.create!(name: "Assignment 1", instructor_id: instructor.id) } + let!(:project_topic) { ProjectTopic.create!(topic_name: "Dummy Topic", assignment_id: assignment.id, max_choosers: 3) } + let!(:team) { Team.create!(assignment_id: assignment.id) } + + describe 'validations' do + it 'validates max_choosers is a non-negative integer' do + expect(project_topic).to be_valid + project_topic.max_choosers = -1 + expect(project_topic).not_to be_valid + end + end + + describe '#slot_available?' do + context 'when no teams have chosen the topic' do + it 'returns true' do + expect(project_topic.slot_available?).to be true + end + end + + context 'when no slots are available' do + before do + project_topic.max_choosers.times do + SignedUpTeam.create!(sign_up_topic_id: project_topic.id, is_waitlisted: false, team: Team.create!(assignment_id: assignment.id)) + end + end + + it 'returns false' do + expect(project_topic.slot_available?).to be false + end + end + end + + describe '#signup_team' do + let(:new_team) { Team.create!(assignment_id: assignment.id) } + + context 'when slot is available' do + it 'assigns the topic to the team and drops team waitlists' do + allow(SignedUpTeam).to receive(:drop_off_topic_waitlists).with(new_team.id).and_return(true) + + expect(project_topic.signup_team(new_team.id)).to be true + expect(SignedUpTeam).to have_received(:drop_off_topic_waitlists).with(new_team.id) + end + end + + context 'when no slots are available' do + before do + project_topic.max_choosers.times do + SignedUpTeam.create!(sign_up_topic_id: project_topic.id, is_waitlisted: false, team: Team.create!(assignment_id: assignment.id)) + end + end + + it 'adds the team to the waitlist' do + expect { project_topic.signup_team(new_team.id) }.to change { SignedUpTeam.count }.by(1) + expect(SignedUpTeam.last.is_waitlisted).to be true + end + end + + context 'when the team is already signed up and not waitlisted' do + before do + SignedUpTeam.create!(sign_up_topic_id: project_topic.id, team: team, is_waitlisted: false) + end + + it 'returns false' do + expect(project_topic.signup_team(team.id)).to be false + end + end + end + + describe '#longest_waiting_team' do + let!(:waitlisted_team) { SignedUpTeam.create!(sign_up_topic_id: project_topic.id, is_waitlisted: true, created_at: 1.day.ago, team: Team.create!(assignment_id: assignment.id)) } + + it 'returns the team that has been waitlisted the longest' do + expect(project_topic.longest_waiting_team).to eq(waitlisted_team) + end + end + + describe '#drop_team_from_topic' do + let!(:signed_up_team) { SignedUpTeam.create!(sign_up_topic_id: project_topic.id, team: team) } + + it 'removes the team from the topic' do + expect { project_topic.drop_team_from_topic(team.id) }.to change { SignedUpTeam.count }.by(-1) + end + + context 'when the team is not waitlisted' do + let!(:waitlisted_team) { SignedUpTeam.create!(sign_up_topic_id: project_topic.id, is_waitlisted: true, created_at: 1.day.ago, team: Team.create!(assignment_id: assignment.id)) } + + it 'assigns the topic to the next waitlisted team' do + project_topic.drop_team_from_topic(team.id) + expect(waitlisted_team.reload.is_waitlisted).to be false + end + end + end + + describe '#current_available_slots' do + context 'when no teams have signed up' do + it 'returns max_choosers as available slots' do + expect(project_topic.current_available_slots).to eq(project_topic.max_choosers) + end + end + + context 'when some teams have signed up' do + before do + 2.times do + SignedUpTeam.create!(sign_up_topic_id: project_topic.id, is_waitlisted: false, team: Team.create!(assignment_id: assignment.id)) + end + end + + it 'returns the remaining available slots' do + expect(project_topic.current_available_slots).to eq(1) + end + end + end +end diff --git a/spec/models/signed_up_team_spec.rb b/spec/models/signed_up_team_spec.rb new file mode 100644 index 000000000..d6281800a --- /dev/null +++ b/spec/models/signed_up_team_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +RSpec.describe SignedUpTeam, type: :model do + let!(:role) { Role.create!(name: "Professor") } + let!(:instructor) { Instructor.create!(name: "Ed", password: "sec-Key1", full_name: "Ed Gehringer", email: "efg@ncsu.edu", role_id: role.id) } + let!(:assignment) { Assignment.create!(name: "Assignment 1", instructor_id: instructor.id) } + let!(:project_topic) { ProjectTopic.create!(topic_name: "Dummy Topic", assignment_id: assignment.id, max_choosers: 3) } + let!(:team) { Team.create!(assignment_id: assignment.id) } + + describe '.get_team_id_for_user' do + let!(:user) { User.create!(name: "Name", password: "password", full_name: "Full Name", email: "email@example.com", mru_directory_path: "/dummy/path", role_id: role.id) } + let!(:teams_user) { TeamsUser.create!(team_id: team.id, user_id: user.id) } + + it 'returns the correct team ID for the given user and assignment' do + expect(SignedUpTeam.get_team_id_for_user(user.id, assignment.id)).to eq(team.id) + end + + it 'returns nil if the user is not associated with any team for the assignment' do + other_user = User.create!(name: "Name 2", password: "password", full_name: "Full Name", email: "email@example.com", mru_directory_path: "/dummy/path", role_id: role.id) + expect(SignedUpTeam.get_team_id_for_user(other_user.id, assignment.id)).to be_nil + end + end + + describe '.delete_team_signup_records' do + let!(:signup1) { SignedUpTeam.create!(sign_up_topic_id: project_topic.id, is_waitlisted: false, team_id: team.id) } + let!(:project_topic_2) { ProjectTopic.create!(topic_name: "Dummy Topic 2", assignment_id: assignment.id, max_choosers: 3) } + let!(:signup2) { SignedUpTeam.create!(sign_up_topic_id: project_topic_2.id, is_waitlisted: true, team_id: team.id) } + + it 'removes all sign-up records for the given team' do + expect { SignedUpTeam.delete_team_signup_records(team.id) } + .to change { SignedUpTeam.where(team_id: team.id).count }.by(-2) + end + end + + describe '#assign_topic_to_waitlisted_team' do + let!(:project_topic_2) { ProjectTopic.create!(topic_name: "Dummy Topic 2", assignment_id: assignment.id, max_choosers: 3) } + let!(:signed_up_team) { SignedUpTeam.create!(sign_up_topic_id: project_topic.id, is_waitlisted: true, team_id: team.id) } + let!(:signed_up_team_2) { SignedUpTeam.create!(sign_up_topic_id: project_topic_2.id, is_waitlisted: false, team_id: team.id) } + + before do + allow(ProjectTopic).to receive(:find).with(project_topic_2.id).and_return(project_topic_2) + allow(project_topic_2).to receive(:drop_team_from_topic) + end + + it 'reassigns the team to a new topic and marks them as not waitlisted' do + signed_up_team.assign_topic_to_waitlisted_team(project_topic.id) + expect(signed_up_team.is_waitlisted).to be_falsey + expect(project_topic_2).to have_received(:drop_team_from_topic).with(team_id: team.id) + end + end +end diff --git a/spec/requests/api/v1/sign_up_topic_controller_spec.rb b/spec/requests/api/v1/project_topic_controller_spec.rb similarity index 65% rename from spec/requests/api/v1/sign_up_topic_controller_spec.rb rename to spec/requests/api/v1/project_topic_controller_spec.rb index dbbe78673..5909fb449 100644 --- a/spec/requests/api/v1/sign_up_topic_controller_spec.rb +++ b/spec/requests/api/v1/project_topic_controller_spec.rb @@ -1,14 +1,14 @@ require 'swagger_helper' -RSpec.describe 'SignUpTopicController API', type: :request do +RSpec.describe 'ProjectTopicController API', type: :request do - # GET /sign_up_topics - path '/api/v1/sign_up_topics' do - get('Get sign-up topics') do + # GET /project_topics + path '/api/v1/project_topics' do + get('Get project topics') do parameter name: 'assignment_id', in: :query, type: :integer, description: 'Assignment ID', required: true parameter name: 'topic_ids', in: :query, type: :string, description: 'Topic Identifier', required: false - tags 'SignUpTopic' + tags 'ProjectTopic' produces 'application/json' response(200, 'successful') do after do |example| @@ -18,41 +18,32 @@ } } end - # context 'when assignment_id parameter is missing' do - # let(:assignment) { create(:sign_up_topic, assignment_id: create(:assignment)) } - # - # before { get '/api/v1/sign_up_topics', params: { assignment_id: assignment_id } } - # it 'returns an error message with status 422' do - # expect(response).to have_http_status(422) - # expect(response_body).to eq({ message: 'Assignment ID is required!' }) - # end - # end context 'when assignment_id parameter is present' do - let!(:sign_up_topics) { create_list(:sign_up_topic, 3, assignment_id: 1) } + let!(:project_topics) { create_list(:project_topic, 3, assignment_id: 1) } let(:assignment_id) { 1 } context 'when topic_identifier parameter is missing' do - before { get "/api/v1/sign_up_topics?assignment_id=#{assignment_id}" } + before { get "/api/v1/project_topics?assignment_id=#{assignment_id}" } - it 'returns a list of all sign-up topics with the given assignment_id' do + it 'returns a list of all project topics with the given assignment_id' do expect(response).to have_http_status(200) expect(response_body[:message]).to eq('All selected topics have been loaded successfully.') - expect(response_body[:sign_up_topics].count).to eq(3) + expect(response_body[:project_topics].count).to eq(3) end end context 'when topic_identifier parameter is present' do - let!(:sign_up_topic) { create(:sign_up_topic, assignment_id: 1, topic_identifier: 'abc') } + let!(:project_topic) { create(:project_topic, assignment_id: 1, topic_identifier: 'abc') } let(:topic_identifier) { 'abc' } - before { get "/api/v1/sign_up_topics?assignment_id=#{assignment_id}&topic_identifier=#{topic_identifier}" } + before { get "/api/v1/project_topics?assignment_id=#{assignment_id}&topic_identifier=#{topic_identifier}" } - it 'returns a list of sign-up topics with the given assignment_id and topic_identifier' do + it 'returns a list of project topics with the given assignment_id and topic_identifier' do expect(response).to have_http_status(200) expect(response_body[:message]).to eq('All selected topics have been loaded successfully.') - expect(response_body[:sign_up_topics].count).to eq(1) - expect(response_body[:sign_up_topics].first[:topic_identifier]).to eq('abc') + expect(response_body[:project_topics].count).to eq(1) + expect(response_body[:project_topics].first[:topic_identifier]).to eq('abc') end end end @@ -63,13 +54,13 @@ end - # DELETE /sign_up_topics - path '/api/v1/sign_up_topics' do - delete('Delete sign-up topics') do + # DELETE /project_topics + path '/api/v1/project_topics' do + delete('Delete project topics') do parameter name: 'assignment_id', in: :query, type: :integer, description: 'Assignment ID', required: true parameter name: 'topic_ids', in: :query, type: :array, items: { type: :string }, description: 'Topic Identifiers to delete', required: false - tags 'SignUpTopic' + tags 'ProjectTopic' produces 'application/json' response(200, 'successful') do after do |example| @@ -83,7 +74,7 @@ context 'when assignment_id parameter is missing' do let(:assignment_id) { nil } - before { delete '/api/v1/sign_up_topics', params: { assignment_id: assignment_id } } + before { delete '/api/v1/project_topics', params: { assignment_id: assignment_id } } it 'returns an error message with status 422' do expect(response).to have_http_status(422) @@ -95,26 +86,26 @@ context 'when topic_ids parameter is missing' do let(:assignment_id) { 1 } - before { delete "/api/v1/sign_up_topics?assignment_id=#{assignment_id}" } + before { delete "/api/v1/project_topics?assignment_id=#{assignment_id}" } - it 'deletes all sign-up topics with the given assignment_id' do + it 'deletes all project topics with the given assignment_id' do expect(response).to have_http_status(200) - expect(response_body).to eq({ message: 'All sign-up topics have been deleted successfully.' }) - expect(SignUpTopic.where(assignment_id: assignment_id)).to be_empty + expect(response_body).to eq({ message: 'All project topics have been deleted successfully.' }) + expect(ProjectTopic.where(assignment_id: assignment_id)).to be_empty end end context 'when topic_ids parameter is present' do - let!(:sign_up_topic) { create(:sign_up_topic, assignment_id: 1, topic_identifier: 'abc') } + let!(:project_topic) { create(:project_topic, assignment_id: 1, topic_identifier: 'abc') } let(:topic_ids) { ['abc'] } let(:assignment_id) { 1 } - before { delete "/api/v1/sign_up_topics?assignment_id=#{assignment_id}&topic_ids=#{topic_ids.join(',')}" } + before { delete "/api/v1/project_topics?assignment_id=#{assignment_id}&topic_ids=#{topic_ids.join(',')}" } it 'deletes sign-up topics with the given assignment_id and topic_identifier' do expect(response).to have_http_status(200) expect(response_body).to eq({ message: 'All selected topics have been deleted successfully.' }) - expect(SignUpTopic.where(assignment_id: assignment_id, topic_identifier: topic_ids)).to be_empty + expect(ProjectTopic.where(assignment_id: assignment_id, topic_identifier: topic_ids)).to be_empty end end end @@ -122,14 +113,14 @@ end end - # CREATE /sign_up_topics - path '/api/v1/sign_up_topics' do + # CREATE /project_topics + path '/api/v1/project_topics' do post('create a new topic in the sheet') do - tags 'SignUpTopic' + tags 'ProjectTopic' consumes 'application/json' - #inputs are from the sign up topic table with properties as ID, name, choosers + #inputs are from the project topic table with properties as ID, name, choosers # assignment ID and micropayment - parameter name: :sign_up_topic, in: :body, schema: { + parameter name: :project_topic, in: :body, schema: { type: :object, properties: { topic_identifier: { type: :integer }, @@ -158,20 +149,20 @@ let!(:assignment) { create(:assignment) } context 'when the request is valid' do - let(:valid_attributes) { { sign_up_topic: attributes_for(:sign_up_topic, assignment_id: assignment.id), micropayment: 0.1 } } + let(:valid_attributes) { { project_topic: attributes_for(:project_topic, assignment_id: assignment.id), micropayment: 0.1 } } - before { post '/api/v1/sign_up_topics', params: valid_attributes } + before { post '/api/v1/project_topics', params: valid_attributes } - it 'creates a sign-up topic' do + it 'creates a project topic' do expect(response).to have_http_status(:created) - expect(response_body[:message]).to eq("The topic: \"#{SignUpTopic.last.topic_name}\" has been created successfully.") + expect(response_body[:message]).to eq("The topic: \"#{ProjectTopic.last.topic_name}\" has been created successfully.") end end context 'when the request is invalid' do - let(:invalid_attributes) { { sign_up_topic: { topic_name: '' }, micropayment: 0.1, assignment_id: assignment.id } } + let(:invalid_attributes) { { project_topic: { topic_name: '' }, micropayment: 0.1, assignment_id: assignment.id } } - before { post '/api/v1/sign_up_topics', params: invalid_attributes } + before { post '/api/v1/project_topics', params: invalid_attributes } it 'returns an error message' do expect(response).to have_http_status(:unprocessable_entity) @@ -180,9 +171,9 @@ end context 'when the assignment does not exist' do - let(:invalid_attributes) { { sign_up_topic: attributes_for(:sign_up_topic), micropayment: 0.1, assignment_id: 999 } } + let(:invalid_attributes) { { project_topic: attributes_for(:project_topic), micropayment: 0.1, assignment_id: 999 } } - before { post '/api/v1/sign_up_topics', params: invalid_attributes } + before { post '/api/v1/project_topics', params: invalid_attributes } it 'returns an error message' do expect(response).to have_http_status(:unprocessable_entity) @@ -191,42 +182,42 @@ end context 'when the assignment is a microtask' do - let(:valid_attributes) { { sign_up_topic: attributes_for(:sign_up_topic, assignment_id: assignment.id), micropayment: 0.1 } } + let(:valid_attributes) { { project_topic: attributes_for(:project_topic, assignment_id: assignment.id), micropayment: 0.1 } } before do assignment.update(microtask: true) - post '/api/v1/sign_up_topics', params: valid_attributes + post '/api/v1/project_topics', params: valid_attributes end it 'sets the micropayment' do expect(response).to have_http_status(:created) - expect(SignUpTopic.last.micropayment).to eq(0.1) + expect(ProjectTopic.last.micropayment).to eq(0.1) end end context 'when the assignment is not a microtask' do - let(:valid_attributes) { { sign_up_topic: attributes_for(:sign_up_topic, assignment_id: assignment.id), micropayment: 0.1 } } + let(:valid_attributes) { { project_topic: attributes_for(:project_topic, assignment_id: assignment.id), micropayment: 0.1 } } before do assignment.update(microtask: false) - post '/api/v1/sign_up_topics', params: valid_attributes + post '/api/v1/project_topics', params: valid_attributes end it 'does not set the micropayment' do expect(response).to have_http_status(:created) - expect(SignUpTopic.last.micropayment).to be_nil + expect(ProjectTopic.last.micropayment).to be_nil end end end - # UPDATE /sign_up_topics - path '/api/v1/sign_up_topics/{id}' do - parameter name: 'id', in: :path, type: :integer, description: 'id of the sign up topic' + # UPDATE /project_topics + path '/api/v1/project_topics/{id}' do + parameter name: 'id', in: :path, type: :integer, description: 'id of the project topic' put('update a new topic in the sheet') do - tags 'SignUpTopic' + tags 'ProjectTopic' consumes 'application/json' - parameter name: :sign_up_topic, in: :body, schema: { + parameter name: :project_topic, in: :body, schema: { type: :object, properties: { topic_identifier: { type: :integer }, @@ -250,12 +241,12 @@ run_test! end - let(:sign_up_topic) { create(:sign_up_topic) } - let(:url) { "/api/v1/sign_up_topics/#{sign_up_topic.id}" } + let(:project_topic) { create(:project_topic) } + let(:url) { "/api/v1/project_topics/#{project_topic.id}" } context "when valid params are provided" do let(:new_topic_name) { "New Topic Name" } - let(:params) { { sign_up_topic: { topic_name: new_topic_name } } } + let(:params) { { project_topic: { topic_name: new_topic_name } } } before { put url, params: params } @@ -264,8 +255,8 @@ end it "updates the sign-up topic" do - sign_up_topic.reload - expect(sign_up_topic.topic_name).to eq new_topic_name + project_topic.reload + expect(project_topic.topic_name).to eq new_topic_name end it "returns a success message" do @@ -274,7 +265,7 @@ end context "when invalid params are provided" do - let(:params) { { sign_up_topic: { topic_name: "" } } } + let(:params) { { project_topic: { topic_name: "" } } } before { put url, params: params } @@ -282,9 +273,9 @@ expect(response).to have_http_status(422) end - it "does not update the sign-up topic" do - sign_up_topic.reload - expect(sign_up_topic.topic_name).not_to eq("") + it "does not update the project topic" do + project_topic.reload + expect(project_topic.topic_name).not_to eq("") end it "returns an error message" do diff --git a/spec/requests/api/v1/signed_up_team_controller_spec.rb b/spec/requests/api/v1/signed_up_team_controller_spec.rb index 19d365ba8..9b679f774 100644 --- a/spec/requests/api/v1/signed_up_team_controller_spec.rb +++ b/spec/requests/api/v1/signed_up_team_controller_spec.rb @@ -36,7 +36,7 @@ end end - path '/api/v1/signed_up_teams/sign_up_student' do + path '/api/v1/signed_up_teams/signup_user' do parameter name: 'user_id', in: :query, type: :integer, description: 'User ID', required: true post 'Creates a signed up team by student' do tags 'SignedUpTeams' diff --git a/spec/routing/project_topics_routing_spec.rb b/spec/routing/project_topics_routing_spec.rb new file mode 100644 index 000000000..e0509c82d --- /dev/null +++ b/spec/routing/project_topics_routing_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe ProjectTopicsController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/project_topics").to route_to("project_topics#index") + end + + it "routes to #show" do + expect(get: "/project_topics/1").to route_to("project_topics#show", id: "1") + end + + + it "routes to #create" do + expect(post: "/project_topics").to route_to("project_topics#create") + end + + it "routes to #update via PUT" do + expect(put: "/project_topics/1").to route_to("project_topics#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/project_topics/1").to route_to("project_topics#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/project_topics/1").to route_to("project_topics#destroy", id: "1") + end + end +end diff --git a/spec/routing/sign_up_topics_routing_spec.rb b/spec/routing/sign_up_topics_routing_spec.rb deleted file mode 100644 index 5fbf3ed6b..000000000 --- a/spec/routing/sign_up_topics_routing_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "rails_helper" - -RSpec.describe SignUpTopicsController, type: :routing do - describe "routing" do - it "routes to #index" do - expect(get: "/sign_up_topics").to route_to("sign_up_topics#index") - end - - it "routes to #show" do - expect(get: "/sign_up_topics/1").to route_to("sign_up_topics#show", id: "1") - end - - - it "routes to #create" do - expect(post: "/sign_up_topics").to route_to("sign_up_topics#create") - end - - it "routes to #update via PUT" do - expect(put: "/sign_up_topics/1").to route_to("sign_up_topics#update", id: "1") - end - - it "routes to #update via PATCH" do - expect(patch: "/sign_up_topics/1").to route_to("sign_up_topics#update", id: "1") - end - - it "routes to #destroy" do - expect(delete: "/sign_up_topics/1").to route_to("sign_up_topics#destroy", id: "1") - end - end -end diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml index f23cb7b20..2817044d8 100644 --- a/swagger/v1/swagger.yaml +++ b/swagger/v1/swagger.yaml @@ -130,6 +130,351 @@ paths: responses: '204': description: successful + "/api/v1/assignments": + get: + summary: Get assignments + tags: + - Get All Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: assignment successfully + "/api/v1/assignments/{assignment_id}/add_participant/{user_id}": + parameters: + - name: assignment_id + in: path + required: true + schema: + type: string + - name: user_id + in: path + required: true + schema: + type: string + post: + summary: Adds a participant to an assignment + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: participant added successfully + '404': + description: assignment not found + "/api/v1/assignments/{assignment_id}/remove_participant/{user_id}": + parameters: + - name: assignment_id + in: path + required: true + schema: + type: string + - name: user_id + in: path + required: true + schema: + type: string + delete: + summary: Removes a participant from an assignment + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: participant removed successfully + '404': + description: assignment or user not found + "/api/v1/assignments/{assignment_id}/assign_course/{course_id}": + parameters: + - name: assignment_id + in: path + required: true + schema: + type: string + - name: course_id + in: path + required: true + schema: + type: string + patch: + summary: Make course_id of assignment null + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: course_id assigned successfully + '404': + description: assignment not found + "/api/v1/assignments/{assignment_id}/remove_assignment_from_course": + patch: + summary: Removes assignment from course + tags: + - Assignments + parameters: + - name: assignment_id + in: path + required: true + schema: + type: integer + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: assignment removed from course + '404': + description: assignment not found + "/api/v1/assignments/{assignment_id}/copy_assignment": + parameters: + - name: assignment_id + in: path + required: true + schema: + type: string + post: + summary: Copy an existing assignment + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: assignment copied successfully + '404': + description: assignment not found + "/api/v1/assignments/{id}": + parameters: + - name: id + in: path + description: Assignment ID + required: true + schema: + type: integer + delete: + summary: Delete an assignment + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: successful + '404': + description: Assignment not found + "/api/v1/assignments/{assignment_id}/has_topics": + parameters: + - name: assignment_id + in: path + description: Assignment ID + required: true + schema: + type: integer + get: + summary: Check if an assignment has topics + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: successful + '404': + description: Assignment not found + "/api/v1/assignments/{assignment_id}/team_assignment": + parameters: + - name: assignment_id + in: path + description: Assignment ID + required: true + schema: + type: integer + get: + summary: Check if an assignment is a team assignment + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: successful + '404': + description: Assignment not found + "/api/v1/assignments/{assignment_id}/valid_num_review/{review_type}": + parameters: + - name: assignment_id + in: path + description: Assignment ID + required: true + schema: + type: integer + - name: review_type + in: path + description: Review Type + required: true + schema: + type: string + get: + summary: Check if an assignment has a valid number of reviews for a specific + type + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: successful + '404': + description: Assignment not found + "/api/v1/assignments/{assignment_id}/has_teams": + parameters: + - name: assignment_id + in: path + description: Assignment ID + required: true + schema: + type: integer + get: + summary: Check if an assignment has teams + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: successful + '404': + description: Assignment not found + "/api/v1/assignments/{id}/show_assignment_details": + parameters: + - name: id + in: path + description: Assignment ID + required: true + schema: + type: integer + get: + summary: Retrieve assignment details + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: successful + '404': + description: Assignment not found + "/api/v1/assignments/{assignment_id}/varying_rubrics_by_round": + parameters: + - name: assignment_id + in: path + description: Assignment ID + required: true + schema: + type: integer + get: + summary: Check if an assignment has varying rubrics by round + tags: + - Assignments + parameters: + - name: Authorization + in: header + schema: + type: string + - name: Content-Type + in: header + schema: + type: string + responses: + '200': + description: successful + '404': + description: Assignment not found "/api/v1/bookmarks": get: summary: list bookmarks @@ -518,116 +863,345 @@ paths: delete: summary: delete institution tags: - - Institutions + - Institutions + responses: + '200': + description: successful + "/api/v1/invitations": + get: + summary: list invitations + tags: + - Invitations + responses: + '200': + description: Success + post: + summary: create invitation + tags: + - Invitations + parameters: [] + responses: + '201': + description: Create successful + '422': + description: Invalid request + requestBody: + content: + application/json: + schema: + type: object + properties: + assignment_id: + type: integer + from_id: + type: integer + to_id: + type: integer + reply_status: + type: string + required: + - assignment_id + - from_id + - to_id + "/api/v1/invitations/{id}": + parameters: + - name: id + in: path + description: id of the invitation + required: true + schema: + type: integer + get: + summary: show invitation + tags: + - Invitations + responses: + '200': + description: Show invitation + '404': + description: Not found + patch: + summary: update invitation + tags: + - Invitations + parameters: [] + responses: + '200': + description: Update successful + '422': + description: Invalid request + '404': + description: Not found + requestBody: + content: + application/json: + schema: + type: object + properties: + reply_status: + type: string + required: [] + delete: + summary: Delete invitation + tags: + - Invitations + responses: + '204': + description: Delete successful + '404': + description: Not found + "/api/v1/invitations/user/{user_id}/assignment/{assignment_id}": + parameters: + - name: user_id + in: path + description: id of user + required: true + schema: + type: integer + - name: assignment_id + in: path + description: id of assignment + required: true + schema: + type: integer + get: + summary: Show all invitation for the given user and assignment + tags: + - Invitations + responses: + '200': + description: Show all invitations for the user for an assignment + '404': + description: Not found + "/api/v1/join_team_requests/decline/{id}": + parameters: + - name: id + in: path + description: id + required: true + schema: + type: string + post: + summary: decline join_team_request + tags: + - Join Team Requests + responses: + '200': + description: successful + "/api/v1/join_team_requests": + get: + summary: list join_team_requests + tags: + - Join Team Requests + responses: + '200': + description: successful + post: + summary: create join_team_request + parameters: + - name: comments + in: query + description: comments + schema: + type: string + - name: team_id + in: query + description: team_id + schema: + type: integer + - name: assignment_id + in: query + description: assignment_id + schema: + type: integer + tags: + - Join Team Requests + responses: + '200': + description: success + '201': + description: created + '422': + description: unprocessable entity + "/api/v1/join_team_requests/{id}": + parameters: + - name: id + in: path + description: id + required: true + schema: + type: string + get: + summary: show join_team_request + tags: + - Join Team Requests + responses: + '404': + description: not_found + '200': + description: successful + patch: + summary: update join_team_request + parameters: + - name: join_team_request[comments] + in: query + description: comments + schema: + type: string + - name: join_team_request[status] + in: query + description: status + schema: + type: string + tags: + - Join Team Requests + responses: + '200': + description: successful + put: + summary: update join_team_request + parameters: + - name: join_team_request[comments] + in: query + description: comments + schema: + type: string + - name: join_team_request[status] + in: query + description: status + schema: + type: string + tags: + - Join Team Requests + responses: + '200': + description: successful + '422': + description: unprocessable entity + delete: + summary: delete join_team_request + tags: + - Join Team Requests + responses: + '204': + description: successful + '404': + description: not found + "/api/v1/project_topics": + get: + summary: Get project topics + parameters: + - name: assignment_id + in: query + description: Assignment ID + required: true + schema: + type: integer + - name: topic_ids + in: query + description: Topic Identifier + required: false + schema: + type: string + tags: + - ProjectTopic responses: '200': description: successful - "/api/v1/invitations": - get: - summary: list invitations + delete: + summary: Delete project topics + parameters: + - name: assignment_id + in: query + description: Assignment ID + required: true + schema: + type: integer + - name: topic_ids + in: query + items: + type: string + description: Topic Identifiers to delete + required: false + schema: + type: array tags: - - Invitations + - ProjectTopic responses: '200': - description: Success + description: successful post: - summary: create invitation + summary: create a new topic in the sheet tags: - - Invitations + - ProjectTopic parameters: [] responses: '201': - description: Create successful - '422': - description: Invalid request + description: Success requestBody: content: application/json: schema: type: object properties: - assignment_id: - type: integer - from_id: + topic_identifier: type: integer - to_id: + topic_name: + type: string + max_choosers: type: integer - reply_status: + category: type: string + assignment_id: + type: integer + micropayment: + type: integer required: + - topic_identifier + - topic_name + - max_choosers + - category - assignment_id - - from_id - - to_id - "/api/v1/invitations/{id}": + - micropayment + "/api/v1/project_topics/{id}": parameters: - name: id in: path - description: id of the invitation + description: id of the project topic required: true schema: type: integer - get: - summary: show invitation - tags: - - Invitations - responses: - '200': - description: Show invitation - '404': - description: Not found - patch: - summary: update invitation + put: + summary: update a new topic in the sheet tags: - - Invitations + - ProjectTopic parameters: [] responses: '200': - description: Update successful - '422': - description: Invalid request - '404': - description: Not found + description: successful requestBody: content: application/json: schema: type: object properties: - reply_status: + topic_identifier: + type: integer + topic_name: type: string - required: [] - delete: - summary: Delete invitation - tags: - - Invitations - responses: - '204': - description: Delete successful - '404': - description: Not found - "/api/v1/invitations/user/{user_id}/assignment/{assignment_id}": - parameters: - - name: user_id - in: path - description: id of user - required: true - schema: - type: integer - - name: assignment_id - in: path - description: id of assignment - required: true - schema: - type: integer - get: - summary: Show all invitation for the given user and assignment - tags: - - Invitations - responses: - '200': - description: Show all invitations for the user for an assignment - '404': - description: Not found + max_choosers: + type: integer + category: + type: string + assignment_id: + type: integer + micropayment: + type: integer + required: + - topic_identifier + - topic_name + - category + - assignment_id "/api/v1/questionnaires": get: summary: list questionnaires @@ -1038,121 +1612,6 @@ paths: responses: '204': description: successful - "/api/v1/sign_up_topics": - get: - summary: Get sign-up topics - parameters: - - name: assignment_id - in: query - description: Assignment ID - required: true - schema: - type: integer - - name: topic_ids - in: query - description: Topic Identifier - required: false - schema: - type: string - tags: - - SignUpTopic - responses: - '200': - description: successful - delete: - summary: Delete sign-up topics - parameters: - - name: assignment_id - in: query - description: Assignment ID - required: true - schema: - type: integer - - name: topic_ids - in: query - items: - type: string - description: Topic Identifiers to delete - required: false - schema: - type: array - tags: - - SignUpTopic - responses: - '200': - description: successful - post: - summary: create a new topic in the sheet - tags: - - SignUpTopic - parameters: [] - responses: - '201': - description: Success - requestBody: - content: - application/json: - schema: - type: object - properties: - topic_identifier: - type: integer - topic_name: - type: string - max_choosers: - type: integer - category: - type: string - assignment_id: - type: integer - micropayment: - type: integer - required: - - topic_identifier - - topic_name - - max_choosers - - category - - assignment_id - - micropayment - "/api/v1/sign_up_topics/{id}": - parameters: - - name: id - in: path - description: id of the sign up topic - required: true - schema: - type: integer - put: - summary: update a new topic in the sheet - tags: - - SignUpTopic - parameters: [] - responses: - '200': - description: successful - requestBody: - content: - application/json: - schema: - type: object - properties: - topic_identifier: - type: integer - topic_name: - type: string - max_choosers: - type: integer - category: - type: string - assignment_id: - type: integer - micropayment: - type: integer - required: - - topic_identifier - - topic_name - - category - - assignment_id "/api/v1/signed_up_teams/sign_up": post: summary: Creates a signed up team @@ -1177,7 +1636,7 @@ paths: required: - team_id - topic_id - "/api/v1/signed_up_teams/sign_up_student": + "/api/v1/signed_up_teams/signup_user": parameters: - name: user_id in: query @@ -1186,7 +1645,7 @@ paths: schema: type: integer post: - summary: Creates a signed up team by student + summary: Creates a signed up team by user tags: - SignedUpTeams parameters: [] @@ -1279,6 +1738,43 @@ paths: description: signed up team deleted '422': description: invalid request + "/api/v1/student_tasks/list": + get: + summary: student tasks list + tags: + - StudentTasks + parameters: + - name: Authorization + in: header + schema: + type: string + responses: + '200': + description: authorized request has proper JSON schema + '401': + description: unauthorized request has error response + "/api/v1/student_tasks/view": + get: + summary: Retrieve a specific student task by ID + tags: + - StudentTasks + parameters: + - name: id + in: query + required: true + schema: + type: Integer + - name: Authorization + in: header + schema: + type: string + responses: + '200': + description: successful retrieval of a student task + '500': + description: participant not found + '401': + description: unauthorized request has error response "/login": post: summary: Logs in a user @@ -1303,34 +1799,6 @@ paths: required: - user_name - password - /api/v1/student_tasks/list: - get: - summary: List all Student Tasks - tags: - - Student Tasks - responses: - '200': - description: An array of student tasks - /api/v1/student_tasks/view: - get: - summary: View a student task - tags: - - Student Tasks - parameters: - - in: query - name: id - schema: - type: string - required: true - description: The ID of the student task to retrieve - responses: - '200': - description: A specific student task - - - - - servers: - url: http://{defaultHost} variables: