diff --git a/app/controllers/api/v1/assignments_controller.rb b/app/controllers/api/v1/assignments_controller.rb index a43c0d9..769cfd7 100644 --- a/app/controllers/api/v1/assignments_controller.rb +++ b/app/controllers/api/v1/assignments_controller.rb @@ -1,20 +1,60 @@ module Api - module V1 - class AssignmentsController < ApplicationController + module V1 + class AssignmentsController < ApplicationController + before_action :validate_ids!, only: [:create] - def index - render json: 'not yet implemented', status: 501 + def index + render json: 'not yet implemented', status: 501 + end + + # POST /courses/:course_id/lms/:lms_id/assignments + def create + # Check if the course_to_lms association exists + course_to_lms = fetch_course_to_lms(params[:course_id], params[:lms_id]) + return unless course_to_lms + # Check if the assignment already exists + return if assignment_exists?(course_to_lms, params[:name], params[:external_assignment_id]) + # Create and render the assignment + create_and_render_assignment(course_to_lms, params[:name], params[:external_assignment_id]) + end + + def destroy + render json: 'not yet implemented', status: 501 + end + + private + + def fetch_course_to_lms(course_id, lms_id) + course_to_lms = CourseToLms.find_by(course_id: course_id, lms_id: lms_id) + unless course_to_lms + render json: { error: 'No such Course_LMS association' }, status: :not_found end + course_to_lms + end - def create - render json: 'not yet implemented', status: 501 + def assignment_exists?(course_to_lms, assignment_name, external_assignment_id) + existing_assignment = Assignment.find_by(course_to_lms_id: course_to_lms.id, name: assignment_name, external_assignment_id: external_assignment_id) + if existing_assignment + render json: { message: 'Record already exists' }, status: :ok + return true end + false + end - def destroy - render json: 'not yet implemented', status: 501 + def create_and_render_assignment(course_to_lms, assignment_name, external_assignment_id) + assignment = Assignment.new(course_to_lms_id: course_to_lms.id, name: assignment_name, external_assignment_id: external_assignment_id) + if assignment.save + render json: assignment, status: :created + else + render json: assignment.errors, status: :unprocessable_entity end + end + def validate_ids! + if params[:name].blank? || params[:external_assignment_id].blank? || params[:course_id].blank? || params[:lms_id].blank? + render json: { error: 'Params required' }, status: :bad_request + end end end end - \ No newline at end of file +end diff --git a/app/controllers/api/v1/lmss_controller.rb b/app/controllers/api/v1/lmss_controller.rb index 499af77..234183e 100644 --- a/app/controllers/api/v1/lmss_controller.rb +++ b/app/controllers/api/v1/lmss_controller.rb @@ -1,10 +1,45 @@ module Api module V1 class LmssController < BaseController - before_action :validate_name!, only: [:create] + before_action :validate_ids!, only: [:create] + # POST /courses/:course_id/lmss def create - render :json => 'not yet implemented'.to_json, status: 501 + course_id = params[:course_id] # course_id from the URL + lms_id = params[:lms_id] # lms_id from the body + external_course_id = params[:external_course_id] # external_course_id from the body + + # Ensure the course exists + unless Course.exists?(course_id) + render json: { error: 'Course not found' }, status: :not_found + return + end + + # Ensure the lms exists + unless Lms.exists?(lms_id) + render json: { error: 'Lms not found' }, status: :not_found + return + end + + # Check if the entry already exists + existing_entry = CourseToLms.find_by(course_id: course_id, lms_id: lms_id, external_course_id: external_course_id) + if existing_entry + render json: { message: 'The association between the specified course and LMS already exists.' }, status: :ok + return + end + + # Create the course_to_lms entry + course_to_lms = CourseToLms.new( + course_id: course_id, + lms_id: lms_id, + external_course_id: external_course_id + ) + + if course_to_lms.save + render json: course_to_lms, status: :created + else + render json: course_to_lms.errors, status: :unprocessable_entity + end end def index @@ -20,9 +55,9 @@ def destroy # TODO: this should be exported to its own (validator) class. # TODO: this validation should also check the config file for the name of lms's. # - def validate_name! - if params['name'].blank? - render :json => 'name parameter is required', status: 401 + def validate_ids! + if params[:course_id].blank? || params[:lms_id].blank? || params[:external_course_id].blank? + render json: { error: 'course_id and lms_id are required' }, status: :bad_request end end end diff --git a/app/models/assignment.rb b/app/models/assignment.rb new file mode 100644 index 0000000..45d1155 --- /dev/null +++ b/app/models/assignment.rb @@ -0,0 +1,7 @@ +# app/models/assignment.rb +class Assignment < ApplicationRecord + belongs_to :course_to_lms + + validates :name, presence: true + validates :external_assignment_id, presence: true + end \ No newline at end of file diff --git a/app/models/course.rb b/app/models/course.rb new file mode 100644 index 0000000..200c66e --- /dev/null +++ b/app/models/course.rb @@ -0,0 +1,6 @@ +# app/models/course.rb + +class Course < ApplicationRecord + + +end \ No newline at end of file diff --git a/app/models/course_to_lms.rb b/app/models/course_to_lms.rb new file mode 100644 index 0000000..70c3f04 --- /dev/null +++ b/app/models/course_to_lms.rb @@ -0,0 +1,11 @@ +# app/models/course_to_lms.rb +class CourseToLms < ApplicationRecord + # Association with Course and Lms + belongs_to :course + belongs_to :lms + + # You can add validations here if needed, for example: + validates :course_id, presence: true + validates :lms_id, presence: true + end + \ No newline at end of file diff --git a/app/models/lms.rb b/app/models/lms.rb new file mode 100644 index 0000000..6f24f6f --- /dev/null +++ b/app/models/lms.rb @@ -0,0 +1,5 @@ +# app/models/lms.rb +class Lms < ApplicationRecord + + +end \ No newline at end of file diff --git a/db/migrate/20240420232708_change_lms_id_to_course_to_lms_id_in_assignments.rb b/db/migrate/20240420232708_change_lms_id_to_course_to_lms_id_in_assignments.rb new file mode 100644 index 0000000..c3a670f --- /dev/null +++ b/db/migrate/20240420232708_change_lms_id_to_course_to_lms_id_in_assignments.rb @@ -0,0 +1,9 @@ +class ChangeLmsIdToCourseToLmsIdInAssignments < ActiveRecord::Migration[7.1] + def change + remove_index :assignments, :lms_id + remove_column :assignments, :lms_id, :bigint + add_column :assignments, :course_to_lms_id, :bigint, null: false + add_index :assignments, :course_to_lms_id + add_foreign_key :assignments, :course_to_lmss, column: :course_to_lms_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 186595a..754221d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,17 +10,17 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_04_20_211809) do +ActiveRecord::Schema[7.1].define(version: 2024_04_20_232708) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "assignments", force: :cascade do |t| - t.bigint "lms_id" t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "external_assignment_id" - t.index ["lms_id"], name: "index_assignments_on_lms_id" + t.bigint "course_to_lms_id", null: false + t.index ["course_to_lms_id"], name: "index_assignments_on_course_to_lms_id" end create_table "course_to_lmss", force: :cascade do |t| @@ -89,7 +89,7 @@ t.index ["email"], name: "index_users_on_email", unique: true end - add_foreign_key "assignments", "lmss" + add_foreign_key "assignments", "course_to_lmss" add_foreign_key "course_to_lmss", "courses" add_foreign_key "course_to_lmss", "lmss" add_foreign_key "extensions", "assignments" diff --git a/spec/controllers/api/v1/assignments_controller_spec.rb b/spec/controllers/api/v1/assignments_controller_spec.rb index c50949f..27f5245 100644 --- a/spec/controllers/api/v1/assignments_controller_spec.rb +++ b/spec/controllers/api/v1/assignments_controller_spec.rb @@ -1,28 +1,72 @@ require 'rails_helper' module Api module V1 - RSpec.describe AssignmentsController, type: :request do - let(:mock_course_id) { 1 } - let(:mock_lms_id) { 1 } + RSpec.describe AssignmentsController do + def json_response + JSON.parse(response.body) + end + + let(:mock_course) { Course.create!(course_name: "Test Course") } + let(:mock_lms) { Lms.create!(lms_name: "Test LMS") } + let(:mock_course_to_lms) { CourseToLms.create!(course_id: mock_course.id, lms_id: mock_lms.id) } + + let(:valid_params) { { name: "Test Assignment", external_assignment_id: "123ABC", course_id: mock_course.id, lms_id: mock_lms.id } } + + before do + mock_course + mock_lms + mock_course_to_lms + end + + after do + Assignment.delete_all + CourseToLms.delete_all + Course.delete_all + Lms.delete_all + end describe "GET /api/v1/courses/:course_id/lmss/:lms_id/assignments" do it "throws a 501 error" do - get "/api/v1/courses/#{mock_course_id}/lmss/#{mock_lms_id}/assignments" + get :index, params: { course_id: mock_course.id, lms_id: mock_lms.id } expect(response).to have_http_status(501) end end describe "POST /api/v1/courses/:course_id/lmss/:lms_id/assignments" do - it "throws a 501 error" do - post "/api/v1/courses/#{mock_course_id}/lmss/#{mock_lms_id}/assignments" - expect(response).to have_http_status(501) + context 'when valid parameters are provided' do + it 'creates a new assignment and returns status :created' do + post :create, params: valid_params + expect(response).to have_http_status(:created) + expect(json_response["name"]).to eq(valid_params[:name]) + expect(json_response["external_assignment_id"]).to eq(valid_params[:external_assignment_id]) + end + end + + context 'when course_to_lms does not exist' do + it 'returns status :not_found' do + post :create, params: { course_id: -1, lms_id: -1, name: "Test Assignment", external_assignment_id: "123ABC" } + expect(response).to have_http_status(:not_found) + expect(response.body).to include('No such Course_LMS association') + end + end + + context 'when the assignment already exists' do + it 'returns status :ok' do + Assignment.create!(course_to_lms_id: mock_course_to_lms.id, name: "Test Assignment", external_assignment_id: "123ABC") + post :create, params: valid_params + expect(response).to have_http_status(:ok) + expect(response.body).to include('Record already exists') + end end + + + end describe "DELETE /api/v1/courses/:course_id/lmss/:lms_id/assignments/:id" do let(:mock_assignment_id) { 1 } it "throws a 501 error" do - delete "/api/v1/courses/#{mock_course_id}/lmss/#{mock_lms_id}/assignments/#{mock_assignment_id}" + delete :destroy, params: { course_id: mock_course.id, lms_id: mock_lms.id, id: mock_assignment_id } expect(response).to have_http_status(501) end end diff --git a/spec/controllers/api/v1/lmss_controller_spec.rb b/spec/controllers/api/v1/lmss_controller_spec.rb index 19c6c27..68d64ca 100644 --- a/spec/controllers/api/v1/lmss_controller_spec.rb +++ b/spec/controllers/api/v1/lmss_controller_spec.rb @@ -2,26 +2,71 @@ module Api module V1 describe LmssController do - let(:mock_course_id) { 16 } - let(:mock_course_name) { 'testCourseName' } - describe 'create' do - it 'throws a 501 error' do - post :create, params: { - course_id: :mock_course_id, - name: :mock_course_name, - } - expect(response.status).to eq(501) + def json_response + JSON.parse(response.body) + end + + before do + # Manually create a course and LMS in the database + @course = Course.create!(course_name: "Mock CS169 Course") + @lms = Lms.create!(lms_name: "Mock Canvas", use_auth_token: true) + @external_course_id = "mock_external_course_id" + end + + after do + # Clean up the specifically created data + CourseToLms.delete_all + Course.delete_all + Lms.delete_all + end + + describe 'POST #create' do + context 'when valid parameters are provided' do + it 'creates a new course_to_lms association and returns status :created' do + post :create, params: { course_id: @course.id, lms_id: @lms.id, external_course_id: @external_course_id} + expect(response).to have_http_status(:created) + expect(json_response['course_id']).to eq(@course.id) + expect(json_response['lms_id']).to eq(@lms.id) + end end - it 'throws a 401 error if the name is not specified' do - post :create, params: { - course_id: :mock_course_id, - } - expect(response.status).to eq(401) - expect(response.body).to eq('name parameter is required') + + context 'when lms_id is missing' do + it 'returns status :bad_request' do + post :create, params: { course_id: @course.id, external_course_id: @external_course_id} + expect(response).to have_http_status(:bad_request) + expect(response.body).to include('course_id and lms_id are required') + end + end + + context 'when course does not exist' do + it 'returns status :not_found' do + post :create, params: { course_id: -1, lms_id: @lms.id, external_course_id: @external_course_id} + expect(response).to have_http_status(:not_found) + expect(response.body).to include('Course not found') + end + end + + context 'when lms does not exist' do + it 'returns status :not_found' do + post :create, params: { course_id: @course.id, lms_id: -1, external_course_id: @external_course_id} + expect(response).to have_http_status(:not_found) + expect(response.body).to include('Lms not found') + end + end + + context 'when the association already exists' do + it 'returns status :ok' do + CourseToLms.create!(course_id: @course.id, lms_id: @lms.id, external_course_id: @external_course_id) + post :create, params: { course_id: @course.id, lms_id: @lms.id, external_course_id: @external_course_id} + expect(response).to have_http_status(:ok) + expect(response.body).to include('The association between the specified course and LMS already exists.') + end end end + + describe 'index' do it 'throws a 501 error' do get :index, params: { course_id: :mock_course_id }