Skip to content

Commit f7c5ce1

Browse files
Allow teachers and owners to list SchoolEmailDomains
Add SchoolEmailDomainsController and school_email_domains route. Return a list of domain strings. Update CanCan ability to allow access only for teachers and owners. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ec60963 commit f7c5ce1

5 files changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
module Api
4+
class SchoolEmailDomainsController < ApiController
5+
before_action :authorize_user
6+
load_and_authorize_resource :school
7+
authorize_resource :school_email_domain, class: false
8+
9+
def index
10+
render json: school_email_domains, status: :ok
11+
end
12+
13+
private
14+
15+
def school_email_domains
16+
@school.school_email_domains.pluck(:domain)
17+
end
18+
end
19+
end

app/models/ability.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def define_school_owner_abilities(school:)
7272
can(%i[read update destroy], Lesson, school_id: school.id, visibility: %w[teachers students public])
7373
can(%i[read destroy], Feedback, school_project: { school_id: school.id })
7474
can(%i[exchange_code], :google_auth)
75+
can(%i[read create], :school_email_domain)
7576
end
7677

7778
def define_school_teacher_abilities(user:, school:)
@@ -102,6 +103,7 @@ def define_school_teacher_abilities(user:, school:)
102103
can(%i[show_status unsubmit return complete], SchoolProject, project: { remixed_from_id: teacher_project_ids })
103104
can(%i[read create destroy], Feedback, school_project: { project: { remixed_from_id: teacher_project_ids } })
104105
can(%i[exchange_code], :google_auth)
106+
can(%i[read create], :school_email_domain)
105107
end
106108

107109
def define_school_student_abilities(user:, school:)

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
post :batch, on: :collection, to: 'school_students#create_batch'
8383
delete :batch, on: :collection, to: 'school_students#destroy_batch'
8484
end
85+
resources :school_email_domains, only: %i[index], controller: 'school_email_domains'
8586
end
8687

8788
resources :lessons, only: %i[index create show update destroy] do
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
FactoryBot.define do
4+
factory :school_email_domain do
5+
school
6+
sequence(:domain) { |n| "domain#{n}.example.edu" }
7+
end
8+
end
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'Listing school email domains', type: :request do
6+
let(:headers) { { Authorization: UserProfileMock::TOKEN } }
7+
let(:school) { create(:school) }
8+
let(:school_email_domains) { create_list(:school_email_domain, 5, school: school) }
9+
10+
before do
11+
school_email_domains
12+
end
13+
14+
describe '#index' do
15+
shared_examples 'a successful school email domains index' do
16+
it 'responds 200 OK' do
17+
expect(response).to have_http_status(:ok)
18+
end
19+
20+
context 'when the school has domains' do
21+
it 'returns a list of domains for the school' do
22+
data = JSON.parse(response.body, symbolize_names: true)
23+
expect(data).to match_array(school.school_email_domains.pluck(:domain))
24+
end
25+
end
26+
27+
context 'when domains do not belong to the school' do
28+
let(:other_school) { create(:school) }
29+
let(:school_email_domains) { create_list(:school_email_domain, 5, school: other_school) }
30+
31+
it 'returns an empty array' do
32+
data = JSON.parse(response.body, symbolize_names: true)
33+
expect(data).to eq([])
34+
end
35+
end
36+
end
37+
38+
context 'with an authorised owner' do
39+
let(:owner) { create(:owner, school:, name: 'School Owner') }
40+
41+
before do
42+
authenticated_in_hydra_as(owner)
43+
get("/api/schools/#{school.id}/school_email_domains", headers:)
44+
end
45+
46+
it_behaves_like 'a successful school email domains index'
47+
end
48+
49+
context 'with an authorised teacher' do
50+
let(:teacher) { create(:teacher, school:, name: 'School Teacher') }
51+
52+
before do
53+
authenticated_in_hydra_as(teacher)
54+
get("/api/schools/#{school.id}/school_email_domains", headers:)
55+
end
56+
57+
it_behaves_like 'a successful school email domains index'
58+
end
59+
60+
context 'when the user does not have access' do
61+
it 'responds 403 Forbidden when the user is a school-owner for a different school' do
62+
other_school = create(:school)
63+
other_owner = create(:owner, school: other_school, name: 'School Owner')
64+
authenticated_in_hydra_as(other_owner)
65+
66+
get("/api/schools/#{school.id}/school_email_domains", headers:)
67+
expect(response).to have_http_status(:forbidden)
68+
end
69+
70+
it 'responds 403 Forbidden when the user is a school-teacher for a different school' do
71+
other_school = create(:school)
72+
other_teacher = create(:teacher, school: other_school, name: 'School Teacher')
73+
authenticated_in_hydra_as(other_teacher)
74+
75+
get("/api/schools/#{school.id}/school_email_domains", headers:)
76+
expect(response).to have_http_status(:forbidden)
77+
end
78+
79+
it 'responds 403 Forbidden when the user is a student at the school' do
80+
student = create(:student, school:)
81+
authenticated_in_hydra_as(student)
82+
83+
get("/api/schools/#{school.id}/school_email_domains", headers:)
84+
expect(response).to have_http_status(:forbidden)
85+
end
86+
end
87+
88+
context 'when the user is not authenticated' do
89+
it 'responds 401 Unauthorized when no token is given' do
90+
get "/api/schools/#{school.id}/school_email_domains"
91+
expect(response).to have_http_status(:unauthorized)
92+
end
93+
end
94+
end
95+
end

0 commit comments

Comments
 (0)