@@ -16,11 +16,62 @@ defmodule Cadet.Assessments do
16
16
@ xp_early_submission_max_bonus 100
17
17
@ xp_bonus_assessment_type ~w( mission sidequest) a
18
18
@ submit_answer_roles ~w( student) a
19
+ @ change_dates_assessment_role ~w( staff admin) a
20
+ @ delete_assessment_role ~w( staff admin) a
21
+ @ publish_assessment_role ~w( staff admin) a
19
22
@ unsubmit_assessment_role ~w( staff admin) a
20
23
@ grading_roles ~w( ) a
21
24
@ see_all_submissions_roles ~w( staff admin) a
22
25
@ open_all_assessment_roles ~w( staff admin) a
23
26
27
+ def change_dates_assessment ( _user = % User { role: role } , id , close_at , open_at ) do
28
+ if role in @ change_dates_assessment_role do
29
+ assessment = Repo . get ( Assessment , id )
30
+ previous_open_time = assessment . open_at
31
+
32
+ cond do
33
+ Timex . before? ( close_at , open_at ) ->
34
+ { :error , { :bad_request , "New end date should occur after new opening date" } }
35
+
36
+ Timex . before? ( close_at , Timex . now ( ) ) ->
37
+ { :error , { :bad_request , "New end date should occur after current time" } }
38
+
39
+ Timex . equal? ( previous_open_time , open_at ) or Timex . after? ( previous_open_time , Timex . now ( ) ) ->
40
+ update_assessment ( id , % { close_at: close_at , open_at: open_at } )
41
+
42
+ Timex . before? ( open_at , Timex . now ( ) ) ->
43
+ { :error , { :bad_request , "New Opening date should occur after current time" } }
44
+
45
+ true ->
46
+ { :error , { :unauthorized , "Assessment is already opened" } }
47
+ end
48
+ else
49
+ { :error , { :forbidden , "User is not permitted to edit" } }
50
+ end
51
+ end
52
+
53
+ def toggle_publish_assessment ( _publisher = % User { role: role } , id , toggle_publish_to ) do
54
+ if role in @ publish_assessment_role do
55
+ update_assessment ( id , % { is_published: toggle_publish_to } )
56
+ else
57
+ { :error , { :forbidden , "User is not permitted to publish" } }
58
+ end
59
+ end
60
+
61
+ def delete_assessment ( _deleter = % User { role: role } , id ) do
62
+ if role in @ delete_assessment_role do
63
+ assessment = Repo . get ( Assessment , id )
64
+
65
+ Submission
66
+ |> where ( assessment_id: ^ id )
67
+ |> Repo . delete_all ( )
68
+
69
+ Repo . delete ( assessment )
70
+ else
71
+ { :error , { :forbidden , "User is not permitted to delete" } }
72
+ end
73
+ end
74
+
24
75
@ spec user_total_xp ( % User { } ) :: integer ( )
25
76
def user_total_xp ( % User { id: user_id } ) when is_ecto_id ( user_id ) do
26
77
total_xp_bonus =
@@ -163,11 +214,19 @@ defmodule Cadet.Assessments do
163
214
164
215
def assessment_with_questions_and_answers ( id , user = % User { } , password )
165
216
when is_ecto_id ( id ) do
217
+ role = user . role
218
+
166
219
assessment =
167
- Assessment
168
- |> where ( id: ^ id )
169
- |> where ( is_published: true )
170
- |> Repo . one ( )
220
+ if role in @ open_all_assessment_roles do
221
+ Assessment
222
+ |> where ( id: ^ id )
223
+ |> Repo . one ( )
224
+ else
225
+ Assessment
226
+ |> where ( id: ^ id )
227
+ |> where ( is_published: true )
228
+ |> Repo . one ( )
229
+ end
171
230
172
231
if assessment do
173
232
assessment_with_questions_and_answers ( assessment , user , password )
@@ -210,7 +269,7 @@ defmodule Cadet.Assessments do
210
269
Returns a list of assessments with all fields and an indicator showing whether it has been attempted
211
270
by the supplied user
212
271
"""
213
- def all_published_assessments ( user = % User { } ) do
272
+ def all_assessments ( user = % User { } ) do
214
273
assessments =
215
274
Query . all_assessments_with_max_xp_and_grade ( )
216
275
|> subquery ( )
@@ -240,7 +299,7 @@ defmodule Cadet.Assessments do
240
299
question_count: q_count . count ,
241
300
graded_count: a_count . count
242
301
} )
243
- |> where ( is_published: true )
302
+ |> filter_published_assessments ( user )
244
303
|> order_by ( :open_at )
245
304
|> Repo . all ( )
246
305
|> Enum . map ( fn assessment = % Assessment { } ->
@@ -259,6 +318,15 @@ defmodule Cadet.Assessments do
259
318
{ :ok , assessments }
260
319
end
261
320
321
+ def filter_published_assessments ( assessments , user ) do
322
+ role = user . role
323
+
324
+ case role do
325
+ :student -> where ( assessments , is_published: true )
326
+ _ -> assessments
327
+ end
328
+ end
329
+
262
330
defp build_grading_status ( submission_status , a_type , q_count , g_count ) do
263
331
case a_type do
264
332
type when type in [ :mission , :sidequest ] ->
@@ -283,33 +351,101 @@ defmodule Cadet.Assessments do
283
351
@ doc """
284
352
The main function that inserts or updates assessments from the XML Parser
285
353
"""
286
- @ spec insert_or_update_assessments_and_questions ( map ( ) , [ map ( ) ] ) ::
354
+ @ spec insert_or_update_assessments_and_questions ( map ( ) , [ map ( ) ] , boolean ( ) ) ::
287
355
{ :ok , any ( ) }
288
356
| { :error , Ecto.Multi . name ( ) , any ( ) , % { optional ( Ecto.Multi . name ( ) ) => any ( ) } }
289
- def insert_or_update_assessments_and_questions ( assessment_params , questions_params ) do
357
+ def insert_or_update_assessments_and_questions (
358
+ assessment_params ,
359
+ questions_params ,
360
+ force_update
361
+ ) do
290
362
assessment_multi =
291
363
Multi . insert_or_update (
292
364
Multi . new ( ) ,
293
365
:assessment ,
294
- insert_or_update_assessment_changeset ( assessment_params )
366
+ insert_or_update_assessment_changeset ( assessment_params , force_update )
295
367
)
296
368
297
- questions_params
298
- |> Enum . with_index ( 1 )
299
- |> Enum . reduce ( assessment_multi , fn { question_params , index } , multi ->
300
- Multi . run ( multi , String . to_atom ( "question#{ index } " ) , fn _repo ,
301
- % { assessment: % Assessment { id: id } } ->
302
- question_params
303
- |> Map . put ( :display_order , index )
304
- |> build_question_changeset_for_assessment_id ( id )
305
- |> Repo . insert ( )
369
+ if force_update and invalid_force_update ( assessment_multi , questions_params ) do
370
+ { :error , "Question count is different" }
371
+ else
372
+ questions_params
373
+ |> Enum . with_index ( 1 )
374
+ |> Enum . reduce ( assessment_multi , fn { question_params , index } , multi ->
375
+ Multi . run ( multi , String . to_atom ( "question#{ index } " ) , fn _repo ,
376
+ % { assessment: % Assessment { id: id } } ->
377
+ question_exists =
378
+ Repo . exists? (
379
+ where ( Question , [ q ] , q . assessment_id == ^ id and q . display_order == ^ index )
380
+ )
381
+
382
+ # the !question_exists check allows for force updating of brand new assessments
383
+ if ! force_update or ! question_exists do
384
+ question_params
385
+ |> Map . put ( :display_order , index )
386
+ |> build_question_changeset_for_assessment_id ( id )
387
+ |> Repo . insert ( )
388
+ else
389
+ params =
390
+ question_params
391
+ |> Map . put_new ( :max_xp , 0 )
392
+ |> Map . put ( :display_order , index )
393
+
394
+ % { id: question_id , type: type } =
395
+ Question
396
+ |> where ( [ q ] , q . display_order == ^ index and q . assessment_id == ^ id )
397
+ |> Repo . one ( )
398
+
399
+ if question_params . type != Atom . to_string ( type ) do
400
+ { :error ,
401
+ create_invalid_changeset_with_error (
402
+ :question ,
403
+ "Question types should remain the same"
404
+ ) }
405
+ else
406
+ changeset =
407
+ Question . changeset ( % Question { assessment_id: id , id: question_id } , params )
408
+
409
+ Repo . update ( changeset )
410
+ end
411
+ end
412
+ end )
306
413
end )
307
- end )
308
- |> Repo . transaction ( )
414
+ |> Repo . transaction ( )
415
+ end
309
416
end
310
417
311
- @ spec insert_or_update_assessment_changeset ( map ( ) ) :: Ecto.Changeset . t ( )
312
- defp insert_or_update_assessment_changeset ( params = % { number: number } ) do
418
+ # Function that checks if the force update is invalid. The force update is only invalid
419
+ # if the new question count is different from the old question count.
420
+ defp invalid_force_update ( assessment_multi , questions_params ) do
421
+ assessment_id =
422
+ ( assessment_multi . operations
423
+ |> List . first ( )
424
+ |> elem ( 1 )
425
+ |> elem ( 1 ) ) . data . id
426
+
427
+ if assessment_id do
428
+ open_date = Repo . get ( Assessment , assessment_id ) . open_at
429
+ # check if assessment is already opened
430
+ if Timex . after? ( open_date , Timex . now ( ) ) do
431
+ false
432
+ else
433
+ existing_questions_count =
434
+ Question
435
+ |> where ( [ q ] , q . assessment_id == ^ assessment_id )
436
+ |> Repo . all ( )
437
+ |> Enum . count ( )
438
+
439
+ new_questions_count = Enum . count ( questions_params )
440
+ existing_questions_count != new_questions_count
441
+ end
442
+ else
443
+ false
444
+ end
445
+ end
446
+
447
+ @ spec insert_or_update_assessment_changeset ( map ( ) , boolean ( ) ) :: Ecto.Changeset . t ( )
448
+ defp insert_or_update_assessment_changeset ( params = % { number: number } , force_update ) do
313
449
Assessment
314
450
|> where ( number: ^ number )
315
451
|> Repo . one ( )
@@ -318,18 +454,30 @@ defmodule Cadet.Assessments do
318
454
Assessment . changeset ( % Assessment { } , params )
319
455
320
456
assessment ->
321
- if Timex . after? ( assessment . open_at , Timex . now ( ) ) do
322
- # Delete all existing questions
323
- % { id: assessment_id } = assessment
457
+ cond do
458
+ Timex . after? ( assessment . open_at , Timex . now ( ) ) ->
459
+ # Delete all existing questions
460
+ % { id: assessment_id } = assessment
324
461
325
- Question
326
- |> where ( assessment_id: ^ assessment_id )
327
- |> Repo . delete_all ( )
462
+ Question
463
+ |> where ( assessment_id: ^ assessment_id )
464
+ |> Repo . delete_all ( )
328
465
329
- Assessment . changeset ( assessment , params )
330
- else
331
- # if the assessment is already open, don't mess with it
332
- create_invalid_changeset_with_error ( :assessment , "is already open" )
466
+ Assessment . changeset ( assessment , params )
467
+
468
+ force_update ->
469
+ # Maintain the same open/close date when force updating an assessment
470
+ new_params =
471
+ params
472
+ |> Map . delete ( :open_at )
473
+ |> Map . delete ( :close_at )
474
+ |> Map . delete ( :is_published )
475
+
476
+ Assessment . changeset ( assessment , new_params )
477
+
478
+ true ->
479
+ # if the assessment is already open, don't mess with it
480
+ create_invalid_changeset_with_error ( :assessment , "is already open" )
333
481
end
334
482
end
335
483
end
0 commit comments