Skip to content

Commit 3669463

Browse files
authored
Merge branch 'main' into 1069-add-immediate-onboarding
2 parents 96d14f8 + bbb4d69 commit 3669463

16 files changed

Lines changed: 836 additions & 83 deletions

app/controllers/api/lessons_controller.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ class LessonsController < ApiController
77
load_and_authorize_resource :lesson
88

99
def index
10-
archive_scope = params[:include_archived] == 'true' ? Lesson : Lesson.unarchived
11-
scope = params[:school_class_id] ? archive_scope.where(school_class_id: params[:school_class_id]) : archive_scope
12-
ordered_scope = scope.order(created_at: :asc)
13-
14-
accessible_lessons = ordered_scope.accessible_by(current_ability).includes(project: :remixes)
10+
accessible_lessons = filtered_lessons_scope
11+
.accessible_by(current_ability)
12+
.includes(project: :remixes)
1513
@lessons_with_users = accessible_lessons.with_users
14+
1615
if current_user&.school_teacher?(school) || current_user&.school_owner?(school)
1716
render :teacher_index, formats: [:json], status: :ok
1817
else
@@ -75,6 +74,13 @@ def destroy
7574

7675
private
7776

77+
def filtered_lessons_scope
78+
archive_scope = params[:include_archived] == 'true' ? Lesson : Lesson.unarchived
79+
scope = params[:school_class_id] ? archive_scope.where(school_class_id: params[:school_class_id]) : archive_scope
80+
scope = scope.joins(:project).where(projects: { identifier: params[:project_identifier] }) if params[:project_identifier].present?
81+
scope.order(created_at: :asc)
82+
end
83+
7884
def verify_school_class_belongs_to_school
7985
return if base_params[:school_class_id].blank?
8086
return if school&.classes&.pluck(:id)&.include?(base_params[:school_class_id])

app/controllers/api/school_students_controller.rb

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,62 @@ def update
110110
end
111111

112112
def destroy
113-
result = SchoolStudent::Delete.call(school: @school, student_id: params[:id], token: current_user.token)
113+
remove_students([params[:id]])
114+
end
114115

115-
if result.success?
116-
head :no_content
117-
else
118-
render json: { error: result[:error] }, status: :unprocessable_entity
116+
def destroy_batch
117+
# DELETE /api/schools/:school_id/students/batch
118+
# Params: { student_ids: ["uuid1", "uuid2", ...] }
119+
#
120+
# Returns 200 OK with one of:
121+
# - Success: { results: [{ user_id: "..." }, ...] }
122+
# - Partial failure: { results: [...], error: "N student(s) failed to be removed" }
123+
#
124+
# Each result may contain:
125+
# - { user_id:, error: } - deletion failed
126+
# - { user_id:, skipped:, reason: } - student skipped (e.g., not in this school)
127+
# - { user_id: } - deletion succeeded
128+
129+
student_ids = student_ids_params
130+
131+
if student_ids.blank?
132+
render json: {
133+
error: 'No student IDs provided',
134+
error_type: :unprocessable_entity
135+
},
136+
status: :unprocessable_entity
137+
return
119138
end
139+
140+
# Remove duplicates to avoid redundant processing
141+
unique_student_ids = student_ids.uniq
142+
remove_students(unique_student_ids)
120143
end
121144

122145
private
123146

147+
def remove_students(student_ids)
148+
service = StudentRemovalService.new(
149+
students: student_ids,
150+
school: @school,
151+
remove_from_profile: true,
152+
token: current_user.token
153+
)
154+
155+
results = service.remove_students
156+
157+
# Check if any errors occurred
158+
errors = results.select { |r| r[:error] }
159+
if errors.any?
160+
render json: {
161+
results: results,
162+
error: "#{errors.size} student(s) failed to be removed"
163+
}, status: :ok
164+
else
165+
render json: { results: results }, status: :ok
166+
end
167+
end
168+
124169
def school_student_params
125170
params.require(:school_student).permit(:username, :password, :name)
126171
end
@@ -135,6 +180,10 @@ def school_students_params
135180
end
136181
end
137182

183+
def student_ids_params
184+
params.fetch(:student_ids, [])
185+
end
186+
138187
def create_safeguarding_flags
139188
create_teacher_safeguarding_flag
140189
create_owner_safeguarding_flag

app/models/ability.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def define_school_owner_abilities(school:)
6767
can(%i[read create create_batch destroy], ClassStudent, school_class: { school: { id: school.id } })
6868
can(%i[read create destroy], :school_owner)
6969
can(%i[read create destroy], :school_teacher)
70-
can(%i[read create create_batch update destroy], :school_student)
70+
can(%i[read create create_batch update destroy destroy_batch], :school_student)
7171
can(%i[create create_copy], Lesson, school_id: school.id)
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 })

app/models/school.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ class School < ApplicationRecord
2424
if: :united_kingdom?
2525
validates :district_nces_id,
2626
uniqueness: { conditions: -> { where(rejected_at: nil) }, case_sensitive: false, allow_blank: true, message: I18n.t('validations.school.district_nces_id_exists') },
27-
format: { with: /\A\d{12}\z/, allow_nil: true, message: I18n.t('validations.school.district_nces_id') },
28-
presence: true,
27+
format: { with: /\A\d{7}\z/, allow_nil: true, message: I18n.t('validations.school.district_nces_id') },
2928
if: :united_states?
3029
validates :district_name, presence: true, if: :united_states?
3130
validates :school_roll_number,

app/services/student_removal_service.rb

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,29 @@ def initialize(students:, school:, remove_from_profile: false, token: nil)
1111
# Returns an array of hashes, one per student, with details of what was removed
1212
def remove_students
1313
results = []
14+
1415
@students.each do |user_id|
16+
student_roles = Role.student.where(user_id:, school_id: @school.id)
17+
if student_roles.empty?
18+
results << { user_id:, skipped: true, reason: 'no_role_in_school' }
19+
next
20+
end
21+
1522
result = { user_id: }
1623
begin
17-
# Skip if student has projects
18-
projects = Project.where(user_id: user_id)
19-
result[:skipped] = true if projects.length.positive?
24+
ActiveRecord::Base.transaction do
25+
# Delete all projects for this user
26+
projects = Project.where(user_id: user_id)
27+
projects.destroy_all
2028

21-
unless result[:skipped]
22-
ActiveRecord::Base.transaction do
23-
# Remove from classes
24-
class_assignments = ClassStudent.where(student_id: user_id)
25-
class_assignments.destroy_all
29+
# Remove from classes
30+
class_assignments = ClassStudent.joins(:school_class).where(student_id: user_id, school_class: { school_id: @school.id })
31+
class_assignments.destroy_all
2632

27-
# Remove roles
28-
roles = Role.student.where(user_id: user_id)
29-
roles.destroy_all
30-
end
33+
# Remove roles
34+
student_roles.destroy_all
3135

32-
# Remove from profile if requested
36+
# Remove from profile if requested - inside transaction so it can be rolled back
3337
ProfileApiClient.delete_school_student(token: @token, school_id: @school.id, student_id: user_id) if @remove_from_profile && @token.present?
3438
end
3539
rescue StandardError => e

config/locales/en.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ en:
1717
website: "must be a valid URL"
1818
reference: "must be 5-6 digits (e.g., 100000)"
1919
reference_urn_exists: "URN number already exists"
20-
district_nces_id: "must be 12 digits (e.g., 010000000001)"
20+
district_nces_id: "must be 7 digits (e.g., 0100000)"
2121
district_nces_id_exists: "NCES ID already exists"
2222
school_roll_number: "must be numbers followed by letters (e.g., 01572D)"
2323
school_roll_number_exists: "School roll number already exists"

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
resources :teachers, only: %i[index create], controller: 'school_teachers'
7272
resources :students, only: %i[index create update destroy], controller: 'school_students' do
7373
post :batch, on: :collection, to: 'school_students#create_batch'
74+
delete :batch, on: :collection, to: 'school_students#destroy_batch'
7475
end
7576
end
7677

0 commit comments

Comments
 (0)