@@ -2,12 +2,6 @@ defmodule Cadet.Repo.Migrations.MultitenantUpgrade do
22 use Ecto.Migration
33 import Ecto.Query
44
5- alias Cadet.Accounts . { CourseRegistration , Notification }
6- alias Cadet.Assessments . { Answer , Assessment , Question , Submission , SubmissionVotes }
7- alias Cadet.Courses . { AssessmentConfig , Course , Group , Sourcecast }
8- alias Cadet.Repo
9- alias Cadet.Stories.Story
10-
115 def change do
126 # Tracks course configurations
137 create table ( :courses ) do
@@ -139,236 +133,172 @@ defmodule Cadet.Repo.Migrations.MultitenantUpgrade do
139133 execute (
140134 fn ->
141135 # Create the new course for migration
142- { :ok , course } =
143- % Course { }
144- |> Course . changeset ( % {
145- course_name: "CS1101S Programming Methodology (AY21/22 Sem 1)" ,
146- course_short_name: "CS1101S" ,
147- viewable: true ,
148- enable_game: true ,
149- enable_achievments: true ,
150- enable_sourcecast: true ,
151- source_chapter: 1 ,
152- source_variant: "default"
153- } )
154- |> Repo . insert ( )
136+ { 1 , [ course | _ ] } =
137+ repo ( ) . insert_all (
138+ "courses" ,
139+ [
140+ % {
141+ course_name: "CS1101S Programming Methodology (AY21/22 Sem 1)" ,
142+ course_short_name: "CS1101S" ,
143+ viewable: true ,
144+ enable_game: true ,
145+ enable_achievements: true ,
146+ enable_sourcecast: true ,
147+ source_chapter: 1 ,
148+ source_variant: "default" ,
149+ inserted_at: Timex . now ( ) ,
150+ updated_at: Timex . now ( )
151+ }
152+ ] ,
153+ returning: [ :id ]
154+ )
155155
156156 # Namespace existing usernames
157157 from ( u in "users" , update: [ set: [ username: fragment ( "? || ? " , "luminus/" , u . username ) ] ] )
158158 |> repo ( ) . update_all ( [ ] )
159159
160160 # Create course registrations for existing users
161- from ( u in "users" , select: { u . id , u . role , u . group_id , u . game_states } )
162- |> Repo . all ( )
163- |> Enum . each ( fn user ->
164- % CourseRegistration { }
165- |> CourseRegistration . changeset ( % {
166- user_id: elem ( user , 0 ) ,
167- role: elem ( user , 1 ) ,
168- group_id: elem ( user , 2 ) ,
169- game_states: elem ( user , 3 ) ,
170- course_id: course . id
161+ from ( u in "users" ,
162+ select: % {
163+ user_id: u . id ,
164+ role: u . role ,
165+ group_id: u . group_id ,
166+ game_states: u . game_states
167+ }
168+ )
169+ |> repo ( ) . all ( )
170+ |> Enum . map ( fn user ->
171+ Map . merge ( user , % {
172+ course_id: course . id ,
173+ inserted_at: Timex . now ( ) ,
174+ updated_at: Timex . now ( )
171175 } )
172- |> Repo . insert ( )
173176 end )
177+ |> ( & repo ( ) . insert_all ( "course_registrations" , & 1 ) ) . ( )
174178
175179 # Add latest_viewed_id to existing users
176180 repo ( ) . update_all ( "users" , set: [ latest_viewed_id: course . id ] )
177181
178- # Handle groups (adding course_id, and updating leader_id to course registrations)
179- from ( g in "groups" , select: { g . id , g . temp_leader_id } )
180- |> Repo . all ( )
181- |> Enum . each ( fn group ->
182- leader_id =
183- case elem ( group , 1 ) do
184- # leader_id is now going to be non-nullable. if it was previously nil, we will just
185- # assign a staff to be the leader_id during migration
186- nil ->
187- CourseRegistration
188- |> where ( [ cr ] , cr . role in [ :admin , :staff ] )
189- |> Repo . one ( )
190- |> Map . fetch! ( :id )
191-
192- id ->
193- CourseRegistration
194- |> where ( user_id: ^ id )
195- |> Repo . one ( )
196- |> Map . fetch! ( :id )
197- end
198-
199- Group
200- |> where ( id: ^ elem ( group , 0 ) )
201- |> Repo . one ( )
202- |> Group . changeset ( % { leader_id: leader_id , course_id: course . id } )
203- |> Repo . update ( )
204- end )
182+ # Handle groups, adding course_id
183+ repo ( ) . update_all ( "groups" , set: [ course_id: course . id ] )
205184
206185 # Update existing Path questions with new question config
207186 # The questions from other assessment types are not updated as these fields default to false
208187 from ( q in "questions" ,
209188 join: a in "assessments" ,
210189 on: a . id == q . assessment_id ,
211- where: a . type == "path" ,
212- select: q . id
190+ where: a . type == "path"
213191 )
214- |> Repo . all ( )
215- |> Enum . each ( fn question_id ->
216- Question
217- |> Repo . get ( question_id )
218- |> Question . changeset ( % {
192+ |> repo ( ) . update_all (
193+ set: [
219194 show_solution: true ,
220195 build_hidden_testcases: true ,
221196 blocking: true
222- } )
223- |> Repo . update ( )
224- end )
197+ ]
198+ )
225199
226200 # Create Assessment Configurations based on Source Academy Knight
227- [ "Missions" , "Quests" , "Paths" , "Contests" , "Others" ]
228- |> Enum . with_index ( 1 )
229- |> Enum . each ( fn { assessment_type , idx } ->
230- % AssessmentConfig { }
231- |> AssessmentConfig . changeset ( % {
232- order: idx ,
233- type: assessment_type ,
234- course_id: course . id ,
235- show_grading_summary: assessment_type in [ "Missions" , "Quests" ] ,
236- is_manually_graded: assessment_type != "Paths" ,
237- early_submission_xp: 200 ,
238- hours_before_early_xp_decay: 48
239- } )
240- |> Repo . insert ( )
241- end )
201+ { 5 , configs } =
202+ [ "Missions" , "Quests" , "Paths" , "Contests" , "Others" ]
203+ |> Enum . with_index ( 1 )
204+ |> Enum . map ( fn { assessment_type , idx } ->
205+ % {
206+ order: idx ,
207+ type: assessment_type ,
208+ course_id: course . id ,
209+ show_grading_summary: assessment_type in [ "Missions" , "Quests" ] ,
210+ is_manually_graded: assessment_type != "Paths" ,
211+ early_submission_xp: 100 ,
212+ hours_before_early_xp_decay: 24 ,
213+ inserted_at: Timex . now ( ) ,
214+ updated_at: Timex . now ( )
215+ }
216+ end )
217+ |> ( & repo ( ) . insert_all ( "assessment_configs" , & 1 , returning: [ :id ] ) ) . ( )
218+
219+ # assessment_configs = repo().insert_all("assessment_configs", configs, returning: [:id])
242220
243221 # Link existing assessments to an assessment config and course
244- from ( a in "assessments" , select: { a . id , a . type } )
245- |> Repo . all ( )
246- |> Enum . each ( fn assessment ->
247- assessment_type =
248- case elem ( assessment , 1 ) do
249- "mission" -> "Missions"
250- "sidequest" -> "Quests"
251- "path" -> "Paths"
252- "contest" -> "Contests"
253- "practical" -> "Others"
254- end
255-
256- assessment_config =
257- AssessmentConfig
258- |> where ( type: ^ assessment_type )
259- |> Repo . one ( )
260-
261- Assessment
262- |> where ( id: ^ elem ( assessment , 0 ) )
263- |> Repo . one ( )
264- |> Assessment . changeset ( % { config_id: assessment_config . id , course_id: course . id } )
265- |> Repo . update ( )
222+ [
223+ { "mission" , "Missions" } ,
224+ { "sidequest" , "Quests" } ,
225+ { "path" , "Paths" } ,
226+ { "contest" , "Contests" } ,
227+ { "practical" , "Others" }
228+ ]
229+ |> Enum . each ( fn { old_type , new_type } ->
230+ config_id =
231+ from ( ac in "assessment_configs" , where: ac . type == ^ new_type , select: ac . id )
232+ |> repo ( ) . all ( )
233+ |> Enum . at ( 0 )
234+
235+ from ( a in "assessments" , where: a . type == ^ old_type )
236+ |> repo ( ) . update_all (
237+ set: [
238+ config_id: config_id ,
239+ course_id: course . id
240+ ]
241+ )
266242 end )
267243
268244 # Updating student_id and unsubmitted_by_id from User to CourseRegistration
269- from ( s in "submissions" , select: { s . id , s . temp_student_id , s . temp_unsubmitted_by_id } )
270- |> Repo . all ( )
271- |> Enum . each ( fn submission ->
272- student_id =
273- CourseRegistration
274- |> where ( user_id: ^ elem ( submission , 1 ) )
275- |> Repo . one ( )
276- |> Map . fetch! ( :id )
277-
278- unsubmitted_by_id =
279- case elem ( submission , 2 ) do
280- nil ->
281- nil
282-
283- id ->
284- CourseRegistration
285- |> where ( user_id: ^ id )
286- |> Repo . one ( )
287- |> Map . fetch! ( :id )
288- end
289-
290- Submission
291- |> where ( id: ^ elem ( submission , 0 ) )
292- |> Repo . one ( )
293- |> Submission . changeset ( % { student_id: student_id , unsubmitted_by_id: unsubmitted_by_id } )
294- |> Repo . update ( )
295- end )
245+ from (
246+ s in "submissions" ,
247+ join: st in "course_registrations" ,
248+ on: st . user_id == s . temp_student_id ,
249+ update: [ set: [ student_id: st . id ] ]
250+ )
251+ |> repo ( ) . update_all ( [ ] )
296252
297- from ( a in "answers" , select: { a . id , a . temp_grader_id } )
298- |> Repo . all ( )
299- |> Enum . each ( fn answer ->
300- case elem ( answer , 1 ) do
301- nil ->
302- nil
303-
304- user_id ->
305- grader_id =
306- CourseRegistration
307- |> where ( user_id: ^ user_id )
308- |> Repo . one ( )
309- |> Map . fetch! ( :id )
310-
311- Answer
312- |> where ( id: ^ elem ( answer , 0 ) )
313- |> Repo . one ( )
314- |> Answer . grading_changeset ( % { grader_id: grader_id } )
315- |> Repo . update ( )
316- end
317- end )
253+ from (
254+ s in "submissions" ,
255+ join: cr in "course_registrations" ,
256+ on: cr . user_id == s . temp_unsubmitted_by_id ,
257+ update: [ set: [ unsubmitted_by_id: cr . id ] ]
258+ )
259+ |> repo ( ) . update_all ( [ ] )
318260
319- from ( s in "submission_votes" , select: { s . id , s . user_id } )
320- |> Repo . all ( )
321- |> Enum . each ( fn vote ->
322- voter_id =
323- CourseRegistration
324- |> where ( user_id: ^ elem ( vote , 1 ) )
325- |> Repo . one ( )
326- |> Map . fetch! ( :id )
327-
328- SubmissionVotes
329- |> where ( id: ^ elem ( vote , 0 ) )
330- |> Repo . one ( )
331- |> SubmissionVotes . changeset ( % { voter_id: voter_id } )
332- |> Repo . update ( )
333- end )
261+ # Updating grader_id in answer from User to CourseRegistration
262+ from (
263+ a in "answers" ,
264+ join: cr in "course_registrations" ,
265+ on: cr . user_id == a . temp_grader_id ,
266+ update: [ set: [ grader_id: cr . id ] ]
267+ )
268+ |> repo ( ) . update_all ( [ ] )
334269
335- from ( n in "notifications" , select: { n . id , n . user_id } )
336- |> Repo . all ( )
337- |> Enum . each ( fn notification ->
338- course_reg_id =
339- CourseRegistration
340- |> where ( user_id: ^ elem ( notification , 1 ) )
341- |> Repo . one ( )
342- |> Map . fetch! ( :id )
343-
344- Notification
345- |> where ( id: ^ elem ( notification , 0 ) )
346- |> Repo . one ( )
347- |> Notification . changeset ( % { read: true , course_reg_id: course_reg_id } )
348- |> Repo . update ( )
349- end )
270+ # Updating user_id to voter_id of CourseRegistration
271+ from (
272+ s in "submission_votes" ,
273+ join: cr in "course_registrations" ,
274+ on: cr . user_id == s . user_id ,
275+ update: [ set: [ voter_id: cr . id ] ]
276+ )
277+ |> repo ( ) . update_all ( [ ] )
278+
279+ # Updating user_id to course_reg_id in Notification
280+ from (
281+ n in "notifications" ,
282+ join: cr in "course_registrations" ,
283+ on: cr . user_id == n . user_id ,
284+ update: [ set: [ course_reg_id: cr . id ] ]
285+ )
286+ |> repo ( ) . update_all ( [ ] )
350287
351288 # Add course id to all Sourcecasts
352- Sourcecast
353- |> Repo . all ( )
354- |> Enum . each ( fn x ->
355- x
356- |> Sourcecast . changeset ( % { course_id: course . id } )
357- |> Repo . update ( )
358- end )
289+ repo ( ) . update_all ( "sourcecasts" , set: [ course_id: course . id ] )
359290
360291 # Add course id to all Stories
361- Story
362- |> Repo . all ( )
363- |> Enum . each ( fn x ->
364- x
365- |> Story . changeset ( % { course_id: course . id } )
366- |> Repo . update ( )
367- end )
292+ repo ( ) . update_all ( "stories" , set: [ course_id: course . id ] )
368293 end ,
369294 fn -> nil end
370295 )
371296
297+ # Update leader_id to course registrations)
298+ execute (
299+ "update groups g set leader_id = coalesce((select cr.id from course_registrations cr where cr.user_id = g.temp_leader_id), (select cr.id from course_registrations cr where cr.role = 'staff' or cr.role = 'admin'))"
300+ )
301+
372302 # Cleanup users table after data migration
373303 alter table ( :users ) do
374304 remove ( :role )
0 commit comments