From 84832a5a65ce77fabfd26964e470576b3d3a8c03 Mon Sep 17 00:00:00 2001 From: Sarj-Basim Al Harbi <123058662+ba88im@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:01:46 -0800 Subject: [PATCH 1/6] Add project info.yml metadata --- info.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 info.yml diff --git a/info.yml b/info.yml new file mode 100644 index 00000000..447efead --- /dev/null +++ b/info.yml @@ -0,0 +1,32 @@ +project: + name: 'Flextensions' + owner: 'CS169L-23' + teamId: '02' + identities: + heroku: 'https://sp26-02-flextensions-45eafe44bb33.herokuapp.com' + members: + member1: + name: 'Basim' + surname: 'AlHarbi' + githubUsername: 'ba88im' + herokuEmail: 'basimalharbi@berkeley.edu' + member2: + name: 'Igor' + surname: 'Moyzeson' + githubUsername: 'Shklalom' + herokuEmail: 'igormoyzeson@berkeley.edu' + member3: + name: 'Noah' + surname: 'Nizamian' + githubUsername: 'noahnizamian' + herokuEmail: 'nizamian@berkeley.edu' + member4: + name: 'Alexander' + surname: 'Stepanov' + githubUsername: 'alxstx' + herokuEmail: 'alexander.stepanov@berkeley.edu' + member5: + name: 'Anthony' + surname: 'Dam' + githubUsername: 'gobears01' + herokuEmail: 'anthonydam@berkeley.edu' From 41d5d736a08a3eb1e333c217db5f0d963bfb7004 Mon Sep 17 00:00:00 2001 From: Basim Al Harbi <123058662+ba88im@users.noreply.github.com> Date: Thu, 19 Mar 2026 03:28:25 +0300 Subject: [PATCH 2/6] Update info.yml with new formatting instructions --- info.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/info.yml b/info.yml index 447efead..6f546bff 100644 --- a/info.yml +++ b/info.yml @@ -2,31 +2,27 @@ project: name: 'Flextensions' owner: 'CS169L-23' teamId: '02' - identities: - heroku: 'https://sp26-02-flextensions-45eafe44bb33.herokuapp.com' + identities: {} + notifications: + email: 'basimalharbi@berkeley.edu, igormoyzeson@berkeley.edu, nizamian@berkeley.edu, alexander.stepanov@berkeley.edu, anthonydam@berkeley.edu' members: member1: name: 'Basim' surname: 'AlHarbi' githubUsername: 'ba88im' - herokuEmail: 'basimalharbi@berkeley.edu' member2: name: 'Igor' surname: 'Moyzeson' githubUsername: 'Shklalom' - herokuEmail: 'igormoyzeson@berkeley.edu' member3: name: 'Noah' surname: 'Nizamian' githubUsername: 'noahnizamian' - herokuEmail: 'nizamian@berkeley.edu' member4: name: 'Alexander' surname: 'Stepanov' githubUsername: 'alxstx' - herokuEmail: 'alexander.stepanov@berkeley.edu' member5: name: 'Anthony' surname: 'Dam' githubUsername: 'gobears01' - herokuEmail: 'anthonydam@berkeley.edu' From 060014fbd956cf7a2c81eac82f515655bc471c67 Mon Sep 17 00:00:00 2001 From: Noah Nizamian Date: Fri, 20 Feb 2026 20:40:04 -0800 Subject: [PATCH 3/6] Added developer login via omniauth --- .tool-versions | 2 +- Gemfile | 3 ++ Gemfile.lock | 5 ++ app/controllers/session_controller.rb | 30 +++++++++-- app/views/home/index.html.erb | 4 +- config/initializers/omniauth.rb | 3 +- db/seeds.rb | 76 +++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 6 deletions(-) diff --git a/.tool-versions b/.tool-versions index 057186bf..053cba7f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.3.8 +ruby 3.3 diff --git a/Gemfile b/Gemfile index 71cb7e5d..3dc4ebb2 100644 --- a/Gemfile +++ b/Gemfile @@ -60,6 +60,9 @@ gem 'strong_migrations' # Logging Customization gem 'lograge' +# Environment variable management +gem 'dotenv-rails', require: 'dotenv/load' + # Use Active Storage for file uploads [https://guides.rubyonrails.org/active_storage_overview.html] # gem "activestorage", "~> 7.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index 752cd911..5541fc50 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -185,6 +185,10 @@ GEM diff-lcs (1.6.2) docile (1.4.1) domain_name (0.6.20240107) + dotenv (3.2.0) + dotenv-rails (3.2.0) + dotenv (= 3.2.0) + railties (>= 6.1) drb (2.2.3) dumb_delegator (1.1.0) erb (6.0.2) @@ -632,6 +636,7 @@ DEPENDENCIES cucumber-rails database_cleaner-active_record debug + dotenv-rails factory_bot_rails faraday faraday-cookie_jar diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 81fd512c..b77d8645 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -53,15 +53,25 @@ def omniauth_callback 'email' => auth.info.email } creds = auth.credentials # an OmniAuth::AuthHash + + # dev provider doesnt have real credentials so its stubbed + expires_at = creds.expires_at || 30.days.from_now.to_i + refresh_token = creds.refresh_token || 'none' + access_token = OAuth2::AccessToken.new( OAuth2::Client.new('', ''), # client never used – stub creds.token, - refresh_token: creds.refresh_token, - expires_at: creds.expires_at + refresh_token: refresh_token, + expires_at: expires_at ) # Persist / update the user just like `create` - find_or_create_user(user_data, access_token) + user = find_or_create_user(user_data, access_token) + + # Auto-enroll developer login users in test courses + if auth.provider == 'developer' + ensure_developer_test_enrollments(user) + end redirect_to courses_path, notice: "Logged in! Welcome, #{user_data['name']}!" rescue StandardError => e @@ -79,6 +89,18 @@ def destroy private + def ensure_developer_test_enrollments(user) + # Find the test course + test_course = Course.find_by(course_code: 'DEV101') + + # Ensure enrollment in the test course (as student so they can request extensions) + if test_course + UserToCourse.find_or_create_by!(user_id: user.id, course_id: test_course.id) do |utc| + utc.role = 'student' + end + end + end + # TODO: Refactor. def find_or_create_user(user_data, auth_token) auth_token.token @@ -102,6 +124,8 @@ def find_or_create_user(user_data, auth_token) # Store user ID in session for authentication session[:username] = user.name session[:user_id] = user.canvas_uid + + user end # TODO: Move this to a Canvas API libarary or user service diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index d500226b..f356c5f5 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -23,7 +23,9 @@
<%= link_to 'Login', '/auth/canvas', class: 'btn btn-primary', id: 'login-button-index' %> - <%# Login %> + <% if Rails.env.development? %> + <%= link_to 'Developer Login', '/auth/developer', class: 'btn btn-secondary ms-2', id: 'dev-login-button' %> + <% end %>
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 29445eea..bfffa61e 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -26,7 +26,8 @@ def build_access_token client_options: { site: ENV['CANVAS_URL'], authorize_url: "/login/oauth2/auth?scope=#{encoded_scopes}" - } + }, + redirect_uri: "#{ENV['CANVAS_REDIRECT_URI']}/auth/canvas/callback" end # OmniAuth.config.before_request_phase do |env| diff --git a/db/seeds.rb b/db/seeds.rb index 3b17f628..406abc61 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -11,3 +11,79 @@ name: SystemUserService::AUTO_APPROVAL_NAME, canvas_uid: SystemUserService::AUTO_APPROVAL_UID ) + +# Developer test users. you can use teacher or student +if Rails.env.development? + # Teacher user for managing courses + teacher_user = User.find_or_create_by!(email: 'teacher@example.com') do |u| + u.name = 'Test Teacher' + u.admin = false + u.canvas_uid = 'teacher@example.com' + end + + # Student user for requesting extensions + student_user = User.find_or_create_by!(email: 'student@example.com') do |u| + u.name = 'Test Student' + u.admin = false + u.canvas_uid = 'student@example.com' + end + + # Create a single test course + test_course = Course.find_or_create_by!(course_code: 'DEV101') do |c| + c.course_name = 'Development Test Course' + c.canvas_id = 'dev-course-001' + end + + # Link test course to Canvas LMS + CourseToLms.find_or_create_by!(course_id: test_course.id, lms_id: 1) do |ctl| + ctl.external_course_id = 'dev-course-001' + end + + # Enroll teacher as instructor + UserToCourse.find_or_create_by!(user_id: teacher_user.id, course_id: test_course.id) do |utc| + utc.role = 'teacher' + end + + # Enroll student as student + UserToCourse.find_or_create_by!(user_id: student_user.id, course_id: test_course.id) do |utc| + utc.role = 'student' + end + + # Enable extensions for test course + CourseSettings.find_or_create_by!(course_id: test_course.id) do |cs| + cs.enable_extensions = true + end + + # Create form settings for test course + FormSetting.find_or_create_by!(course_id: test_course.id) do |fs| + fs.documentation_disp = 'optional' + fs.custom_q1_disp = 'optional' + fs.custom_q2_disp = 'optional' + end + + # Create sample assignments for test course + course_lms = CourseToLms.find_by(course_id: test_course.id, lms_id: 1) + + if course_lms + Assignment.find_or_create_by!(course_to_lms_id: course_lms.id, external_assignment_id: 'dev-hw-1') do |a| + a.name = 'Homework 1' + a.due_date = 3.days.from_now + a.late_due_date = 10.days.from_now + a.enabled = true + end + + Assignment.find_or_create_by!(course_to_lms_id: course_lms.id, external_assignment_id: 'dev-project-1') do |a| + a.name = 'Project 1' + a.due_date = 7.days.from_now + a.late_due_date = 14.days.from_now + a.enabled = true + end + + Assignment.find_or_create_by!(course_to_lms_id: course_lms.id, external_assignment_id: 'dev-quiz-1') do |a| + a.name = 'Quiz 1' + a.due_date = 5.days.from_now + a.late_due_date = 12.days.from_now + a.enabled = true + end + end +end From 541fab29979b0a73f47c7a48e9bd9eec305c671d Mon Sep 17 00:00:00 2001 From: Noah Nizamian Date: Fri, 20 Feb 2026 23:11:21 -0800 Subject: [PATCH 4/6] Updated tests --- db/seeds.rb | 16 +----- spec/controllers/session_controller_spec.rb | 55 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 406abc61..6457cbea 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -66,24 +66,10 @@ if course_lms Assignment.find_or_create_by!(course_to_lms_id: course_lms.id, external_assignment_id: 'dev-hw-1') do |a| - a.name = 'Homework 1' + a.name = 'Homework' a.due_date = 3.days.from_now a.late_due_date = 10.days.from_now a.enabled = true end - - Assignment.find_or_create_by!(course_to_lms_id: course_lms.id, external_assignment_id: 'dev-project-1') do |a| - a.name = 'Project 1' - a.due_date = 7.days.from_now - a.late_due_date = 14.days.from_now - a.enabled = true - end - - Assignment.find_or_create_by!(course_to_lms_id: course_lms.id, external_assignment_id: 'dev-quiz-1') do |a| - a.name = 'Quiz 1' - a.due_date = 5.days.from_now - a.late_due_date = 12.days.from_now - a.enabled = true - end end end diff --git a/spec/controllers/session_controller_spec.rb b/spec/controllers/session_controller_spec.rb index 65b85f6d..20e869f5 100644 --- a/spec/controllers/session_controller_spec.rb +++ b/spec/controllers/session_controller_spec.rb @@ -111,6 +111,61 @@ end end + describe 'GET #omniauth_callback (developer provider)' do + let(:dev_auth_hash) do + OmniAuth::AuthHash.new( + provider: 'developer', + uid: 'test@example.com', + info: OpenStruct.new(name: 'Test Developer', email: 'test@example.com'), + credentials: { + token: 'dev-token', + refresh_token: nil, + expires_at: nil + } + ) + end + + before do + request.env['omniauth.auth'] = dev_auth_hash + end + + context 'developer provider login with missing credentials' do + it 'handles nil credentials gracefully' do + get :omniauth_callback, params: { provider: 'developer' } + + user = User.find_by(canvas_uid: 'test@example.com') + expect(user).to be_present + expect(user.email).to eq('test@example.com') + + expect(session[:user_id]).to eq('test@example.com') + expect(response).to redirect_to(courses_path) + end + + # test course for dev login + it 'auto-enrolls developer login users in test course' do + test_course = Course.create!(course_code: 'DEV101', course_name: 'Test Course', canvas_id: 'dev-001') + + get :omniauth_callback, params: { provider: 'developer' } + + user = User.find_by(canvas_uid: 'test@example.com') + enrollment = UserToCourse.find_by(user_id: user.id, course_id: test_course.id) + + expect(enrollment).to be_present + expect(enrollment.role).to eq('student') + end + + it 'stores fake refresh token for developer provider' do + get :omniauth_callback, params: { provider: 'developer' } + + user = User.find_by(canvas_uid: 'test@example.com') + creds = user.lms_credentials.first + + expect(creds.refresh_token).to be_present + expect(creds.token).to be_present + end + end + end + describe 'GET #logout' do before do session[:user_id] = 'test_user_id' From bb7dd053c09f8f99a3d51ccf918690dee3410aaf Mon Sep 17 00:00:00 2001 From: Noah Nizamian Date: Fri, 20 Feb 2026 23:17:30 -0800 Subject: [PATCH 5/6] linter fixes --- app/controllers/session_controller.rb | 6 +++--- spec/controllers/session_controller_spec.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index b77d8645..eeac56a9 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -53,11 +53,11 @@ def omniauth_callback 'email' => auth.info.email } creds = auth.credentials # an OmniAuth::AuthHash - + # dev provider doesnt have real credentials so its stubbed expires_at = creds.expires_at || 30.days.from_now.to_i refresh_token = creds.refresh_token || 'none' - + access_token = OAuth2::AccessToken.new( OAuth2::Client.new('', ''), # client never used – stub creds.token, @@ -124,7 +124,7 @@ def find_or_create_user(user_data, auth_token) # Store user ID in session for authentication session[:username] = user.name session[:user_id] = user.canvas_uid - + user end diff --git a/spec/controllers/session_controller_spec.rb b/spec/controllers/session_controller_spec.rb index 20e869f5..6acae4d2 100644 --- a/spec/controllers/session_controller_spec.rb +++ b/spec/controllers/session_controller_spec.rb @@ -142,7 +142,7 @@ end # test course for dev login - it 'auto-enrolls developer login users in test course' do + it 'auto-enrolls developer login users in test course' do test_course = Course.create!(course_code: 'DEV101', course_name: 'Test Course', canvas_id: 'dev-001') get :omniauth_callback, params: { provider: 'developer' } From e8e003f424de555124893e7d4b5893e836acd126 Mon Sep 17 00:00:00 2001 From: Sarj-Basim Al Harbi <123058662+ba88im@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:23:22 -0700 Subject: [PATCH 6/6] Hide checkbox and status columns on mobile requests page Adjust DataTables data-priority values so only Actions, Assignment, and Name columns remain visible on small screens. Bulk action buttons are also hidden on mobile via Bootstrap responsive utility classes. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/views/requests/instructor_index.html.erb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/requests/instructor_index.html.erb b/app/views/requests/instructor_index.html.erb index 4274f8ef..4f6a8d54 100644 --- a/app/views/requests/instructor_index.html.erb +++ b/app/views/requests/instructor_index.html.erb @@ -31,7 +31,7 @@ id="requests-table"> - + Actions - Name - Assignment + Name + Assignment Student ID Requested At Original Due Date - Requested Due Date + Requested Due Date # of Days - Status + Status @@ -143,7 +143,7 @@ -
+