Skip to content

Commit 931a51f

Browse files
committed
feat: let teachers and owners use class join links
Teachers and owners of the class's school can now use the join link: - A teacher already in the class is treated as already_member and redirected into the class. - A teacher of the school not yet in the class gets joinable_as_teacher; POST /api/join adds them as a ClassTeacher, then redirects. - An owner of the school gets the owner status and is redirected into the class without being added to it. Refactors compute_action_status into existing_user_join_status / new_user_join_status, split on whether the user already has a role in the school, and renames add_user_to_school_and_class to add_student_to_school_and_class.
1 parent 0529bb0 commit 931a51f

2 files changed

Lines changed: 82 additions & 19 deletions

File tree

app/controllers/api/join_controller.rb

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ def create
1414
case action_status
1515
when :wrong_school, :domain_mismatch, :not_a_student
1616
render json: { error: action_status.to_s }, status: :forbidden
17-
when :already_member
17+
when :already_member, :owner
18+
render json: { redirect_url: class_redirect_path }, status: :ok
19+
when :joinable_as_teacher
20+
add_user_to_class_as_teacher
1821
render json: { redirect_url: class_redirect_path }, status: :ok
1922
else
20-
add_user_to_school_and_class
23+
add_student_to_school_and_class
2124
render json: { redirect_url: class_redirect_path }, status: :ok
2225
end
2326
end
@@ -41,7 +44,21 @@ def action_status
4144

4245
def compute_action_status
4346
return :already_member if user_is_member_of_class?
44-
return :joinable if user_is_student_of_school?
47+
return existing_user_join_status if user_has_role_in_school?
48+
49+
new_user_join_status
50+
end
51+
52+
# The user already has a role in this school: which one decides the status.
53+
def existing_user_join_status
54+
return :owner if user_is_owner_of_school?
55+
return :joinable_as_teacher if user_is_teacher_of_school?
56+
57+
:joinable # student is the only remaining role for this school
58+
end
59+
60+
# The user has no role in this school yet: may they join as a new student?
61+
def new_user_join_status
4562
return :not_a_student if user_has_non_student_role?
4663
return :wrong_school if user_in_different_school?
4764
return :domain_mismatch unless @school.valid_email?(current_user.email)
@@ -54,11 +71,20 @@ def class_redirect_path
5471
end
5572

5673
def user_is_member_of_class?
57-
ClassStudent.exists?(school_class: @school_class, student_id: current_user.id)
74+
ClassStudent.exists?(school_class: @school_class, student_id: current_user.id) ||
75+
ClassTeacher.exists?(school_class: @school_class, teacher_id: current_user.id)
5876
end
5977

60-
def user_is_student_of_school?
61-
Role.exists?(school: @school, user_id: current_user.id, role: Role.roles[:student])
78+
def user_is_owner_of_school?
79+
Role.exists?(school: @school, user_id: current_user.id, role: Role.roles[:owner])
80+
end
81+
82+
def user_is_teacher_of_school?
83+
Role.exists?(school: @school, user_id: current_user.id, role: Role.roles[:teacher])
84+
end
85+
86+
def user_has_role_in_school?
87+
Role.exists?(school: @school, user_id: current_user.id)
6288
end
6389

6490
def user_has_non_student_role?
@@ -69,9 +95,15 @@ def user_in_different_school?
6995
Role.where(user_id: current_user.id).where.not(school_id: @school.id).exists?
7096
end
7197

72-
def add_user_to_school_and_class
98+
def add_student_to_school_and_class
7399
Role.create!(school: @school, user_id: current_user.id, role: :student) unless Role.exists?(school: @school, user_id: current_user.id)
74100
ClassStudent.create!(school_class: @school_class, student_id: current_user.id)
75101
end
102+
103+
def add_user_to_class_as_teacher
104+
class_teacher = @school_class.teachers.build(teacher_id: current_user.id)
105+
class_teacher.teacher = current_user
106+
class_teacher.save!
107+
end
76108
end
77109
end

spec/requests/join_controller_spec.rb

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,32 @@
7070
expect(data[:status]).to eq('wrong_school')
7171
end
7272

73-
it 'returns status: not_a_student when the user is a teacher of this school' do
73+
it 'returns status: joinable_as_teacher when the user is a teacher of this school not yet in the class' do
7474
create(:teacher_role, school:, user_id: student.id)
7575

7676
get "/api/join/#{school_class.join_code}", headers: headers
7777

7878
data = JSON.parse(response.body, symbolize_names: true)
79-
expect(data[:status]).to eq('not_a_student')
79+
expect(data[:status]).to eq('joinable_as_teacher')
80+
end
81+
82+
it 'returns status: already_member when the user is already a teacher in the class' do
83+
create(:teacher_role, school:, user_id: student.id)
84+
ClassTeacher.create!(school_class:, teacher_id: student.id)
85+
86+
get "/api/join/#{school_class.join_code}", headers: headers
87+
88+
data = JSON.parse(response.body, symbolize_names: true)
89+
expect(data[:status]).to eq('already_member')
8090
end
8191

82-
it 'returns status: not_a_student when the user is an owner of this school' do
92+
it 'returns status: owner when the user is an owner of this school' do
8393
create(:owner_role, school:, user_id: student.id)
8494

8595
get "/api/join/#{school_class.join_code}", headers: headers
8696

8797
data = JSON.parse(response.body, symbolize_names: true)
88-
expect(data[:status]).to eq('not_a_student')
98+
expect(data[:status]).to eq('owner')
8999
end
90100

91101
it 'returns status: not_a_student for a teacher of a different school (not wrong_school)' do
@@ -204,26 +214,47 @@
204214
end
205215
end
206216

207-
it 'responds with 403 not_a_student when the user is a teacher of the school' do
217+
it 'adds the user to the class as a teacher and returns a redirect URL' do
208218
create(:teacher_role, school:, user_id: student.id)
219+
school_class # force creation before the request
209220

210221
expect do
211222
post "/api/join/#{school_class.join_code}", headers: headers
212-
end.not_to change(ClassStudent, :count)
223+
end.to change(ClassTeacher, :count).by(1)
213224

214-
expect(response).to have_http_status(:forbidden)
225+
expect(response).to have_http_status(:ok)
215226
data = JSON.parse(response.body, symbolize_names: true)
216-
expect(data[:error]).to eq('not_a_student')
227+
expect(data[:redirect_url]).to eq("/school/#{school.code}/class/#{school_class.code}")
228+
expect(ClassTeacher.exists?(school_class:, teacher_id: student.id)).to be(true)
229+
expect(ClassStudent.exists?(school_class:, student_id: student.id)).to be(false)
230+
expect(Role.where(user_id: student.id, school:).pluck(:role)).to eq(['teacher'])
217231
end
218232

219-
it 'responds with 403 not_a_student when the user is an owner of the school' do
233+
it 'is idempotent when the user is already a teacher in the class' do
234+
create(:teacher_role, school:, user_id: student.id)
235+
ClassTeacher.create!(school_class:, teacher_id: student.id)
236+
237+
expect do
238+
post "/api/join/#{school_class.join_code}", headers: headers
239+
end.not_to change(ClassTeacher, :count)
240+
241+
expect(response).to have_http_status(:ok)
242+
data = JSON.parse(response.body, symbolize_names: true)
243+
expect(data[:redirect_url]).to eq("/school/#{school.code}/class/#{school_class.code}")
244+
end
245+
246+
it 'redirects an owner into the class without adding them to it' do
220247
create(:owner_role, school:, user_id: student.id)
221248

222-
post "/api/join/#{school_class.join_code}", headers: headers
249+
expect do
250+
post "/api/join/#{school_class.join_code}", headers: headers
251+
end.not_to change(ClassStudent, :count)
223252

224-
expect(response).to have_http_status(:forbidden)
253+
expect(response).to have_http_status(:ok)
225254
data = JSON.parse(response.body, symbolize_names: true)
226-
expect(data[:error]).to eq('not_a_student')
255+
expect(data[:redirect_url]).to eq("/school/#{school.code}/class/#{school_class.code}")
256+
expect(ClassTeacher.exists?(school_class:, teacher_id: student.id)).to be(false)
257+
expect(Role.where(user_id: student.id, school:).pluck(:role)).to eq(['owner'])
227258
end
228259

229260
it 'responds with 404 when the join code does not exist' do

0 commit comments

Comments
 (0)