Skip to content

Commit f6ba44f

Browse files
zetter-rpfmwtrew
andauthored
Transfer ownership script (#662)
Related to RaspberryPiFoundation/digital-editor-issues#1095 ## What's changed? This is a manual task that we often have to do. Make it easier by creating a re-usable script. We weren't able to turn off the safeguarding flag for the old user as the API currently requires a user token to do this so have kept it a manual step. We based this on the documented process here: https://digital-docs.rpf-internal.org/docs/technology/codebases-and-products/editor/processes/code-editor-for-education/transferring-owners ## usage ```bash # To transfer ownership from Jane to John and REMOVE Jane as a teacher: bundle exec rails school_management:transfer_ownership[jane.doe@example.com,john.doe@example.com] # To transfer ownership from Jane to John and KEEP Jane as a teacher: bundle exec rails school_management:transfer_ownership[jane.doe@example.com,john.doe@example.com,true] ``` ## Steps after deploy - Update documentation at https://digital-docs.rpf-internal.org/docs/technology/codebases-and-products/editor/processes/code-editor-for-education/transferring-owners - Run for RaspberryPiFoundation/digital-editor-issues#1095 --------- Co-authored-by: Matthew Trew <matthew.trew@raspberrypi.org>
1 parent df41adb commit f6ba44f

3 files changed

Lines changed: 172 additions & 0 deletions

File tree

config/database.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ default: &default
22
adapter: postgresql
33
encoding: unicode
44
host: <%= ENV.fetch('POSTGRES_HOST', 'db') %>
5+
port: <%= ENV.fetch('POSTGRES_PORT', '5432') %>
56
username: <%= ENV.fetch('POSTGRES_USER', 'no_pg_user_set') %>
67
password: <%= ENV.fetch('POSTGRES_PASSWORD', '') %>
78
pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>

lib/tasks/school_management.rake

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
3+
namespace :school_management do
4+
desc 'Transfer ownership of a school'
5+
task :transfer_ownership, %i[old_owner_email new_owner_email keep_old_owner_as_teacher] => :environment do |_task, args|
6+
old_owner = UserInfoApiClient.find_user_by_email(args[:old_owner_email])
7+
new_owner = UserInfoApiClient.find_user_by_email(args[:new_owner_email])
8+
9+
unless old_owner
10+
Rails.logger.error("No user found for email #{args[:old_owner_email]}. Did you spell it correctly?")
11+
next
12+
end
13+
14+
unless new_owner
15+
Rails.logger.error("No user found for email #{args[:new_owner_email]}. Did you spell it correctly?")
16+
next
17+
end
18+
19+
if Role.exists?(user_id: new_owner[:id], role: 'owner')
20+
Rails.logger.error("User #{new_owner[:id]} is already the owner of a school")
21+
next
22+
end
23+
24+
if School.exists?(creator_id: new_owner[:id])
25+
Rails.logger.error("User #{new_owner[:id]} is already the creator of a school")
26+
next
27+
end
28+
29+
school = Role.find_by(roles: { user_id: old_owner[:id], role: 'owner' }).school
30+
31+
school.transaction do
32+
remove_old_owner(school, old_owner[:id], args[:keep_old_owner_as_teacher])
33+
assign_roles_to_new_owner(school, new_owner[:id])
34+
35+
school.update!(creator_id: new_owner[:id], creator_agree_to_ux_contact: false)
36+
end
37+
38+
Rails.logger.info "Ownership transfered to #{new_owner[:email]} successfully."
39+
Rails.logger.warn '⚠️ You must now manually remove the owner safeguarding flag from the old owner.'
40+
Rails.logger.warn 'Open a bash console on the rpf-profile app: `heroku run bash -a rpf-profile`'
41+
Rails.logger.warn "Remove the owner safeguarding flag from the old owner: `node profile-cli remove-safeguarding-flag #{args[:old_owner_email]} school:owner`"
42+
end
43+
44+
def remove_old_owner(school, user_id, keep_old_owner_as_teacher)
45+
school.roles.owner.find_by(user_id: user_id).destroy!
46+
school.roles.teacher.find_by(user_id: user_id)&.destroy! unless keep_old_owner_as_teacher
47+
end
48+
49+
def assign_roles_to_new_owner(school, user_id)
50+
school.roles.create!(user_id: user_id, role: 'owner')
51+
school.roles.find_or_create_by!(user_id: user_id, role: 'teacher')
52+
end
53+
end
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
require 'rake'
5+
6+
RSpec.describe 'school_management', type: :task do
7+
describe ':transfer_ownership' do
8+
let(:task) { Rake::Task['school_management:transfer_ownership'] }
9+
let(:old_user_id) { SecureRandom.uuid }
10+
let(:new_user_id) { SecureRandom.uuid }
11+
let(:school) { create(:school, creator_id: old_user_id) }
12+
13+
before do
14+
stub_user_info_api_find_by_email(
15+
email: 'old_owner@example.com',
16+
user: { id: old_user_id, email: 'old_owner@example.com' }
17+
)
18+
19+
stub_user_info_api_find_by_email(
20+
email: 'new_owner@example.com',
21+
user: { id: new_user_id, email: 'new_owner@example.com' }
22+
)
23+
24+
create(:owner_role, school:, user_id: old_user_id)
25+
create(:teacher_role, school:, user_id: old_user_id)
26+
end
27+
28+
it "exits early if new owner doesn't exist" do
29+
allow(UserInfoApiClient).to receive(:find_user_by_email)
30+
.with('not_real_owner@example.com')
31+
.and_return(nil)
32+
33+
task.invoke('old_owner@example.com', 'not_real_owner@example.com')
34+
35+
expect(school.creator_id).to eq(old_user_id)
36+
end
37+
38+
it "exits early if old owner doesn't exist" do
39+
allow(UserInfoApiClient).to receive(:find_user_by_email)
40+
.with('not_real_owner@example.com')
41+
.and_return(nil)
42+
43+
task.invoke('old_owner@example.com', 'new_owner@example.com')
44+
45+
expect(school.creator_id).to eq(old_user_id)
46+
end
47+
48+
it 'exits early if new owner is already owner of a school' do
49+
create(:owner_role, school:, user_id: new_user_id)
50+
51+
task.invoke('old_owner@example.com', 'new_owner@example.com')
52+
53+
expect(school.creator_id).to eq(old_user_id)
54+
end
55+
56+
it 'exits early if new owner is already creator of a school' do
57+
create(:school, creator_id: new_user_id)
58+
59+
task.invoke('old_owner@example.com', 'new_owner@example.com')
60+
61+
expect(school.creator_id).to eq(old_user_id)
62+
end
63+
64+
it 'creates owner and teacher roles for the new owner' do
65+
task.invoke('old_owner@example.com', 'new_owner@example.com')
66+
67+
owners = school.roles.owner
68+
owner_user_ids = owners.map(&:user_id)
69+
teachers = school.roles.teacher
70+
teacher_user_ids = teachers.map(&:user_id)
71+
72+
expect(owner_user_ids).to eq([new_user_id])
73+
expect(teacher_user_ids).to include(new_user_id)
74+
end
75+
76+
it 'does not error if new owner already has teacher role' do
77+
create(:teacher_role, school:, user_id: new_user_id)
78+
task.invoke('old_owner@example.com', 'new_owner@example.com')
79+
80+
owners = school.roles.owner
81+
owner_user_ids = owners.map(&:user_id)
82+
teachers = school.roles.teacher
83+
teacher_user_ids = teachers.map(&:user_id)
84+
85+
expect(owner_user_ids).to eq([new_user_id])
86+
expect(teacher_user_ids).to include(new_user_id)
87+
end
88+
89+
it 'keeps the old owner as a teacher if keep parameter is true' do
90+
task.invoke('old_owner@example.com', 'new_owner@example.com', 'true')
91+
teachers = school.roles.teacher
92+
teacher_user_ids = teachers.map(&:user_id)
93+
expect(teacher_user_ids).to include(old_user_id)
94+
end
95+
96+
it 'removes the old owner as a teacher by default' do
97+
task.invoke('old_owner@example.com', 'new_owner@example.com')
98+
teachers = school.roles.teacher
99+
teacher_user_ids = teachers.map(&:user_id)
100+
expect(teacher_user_ids).not_to include(old_user_id)
101+
end
102+
103+
it 'switches creator to the new owner' do
104+
task.invoke('old_owner@example.com', 'new_owner@example.com')
105+
school.reload
106+
expect(school.creator_id).to eq(new_user_id)
107+
end
108+
109+
it 'sets the school UX contact flag to false' do
110+
school.update!(creator_agree_to_ux_contact: true)
111+
112+
task.invoke('old_owner@example.com', 'new_owner@example.com')
113+
school.reload
114+
115+
expect(school.creator_agree_to_ux_contact).to be(false)
116+
end
117+
end
118+
end

0 commit comments

Comments
 (0)