@@ -16,11 +16,56 @@ 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
+ cond do
32
+ Timex . before? ( close_at , open_at ) ->
33
+ { :error , { :bad_request , "New end date should occur after new opening date" } }
34
+
35
+ Timex . before? ( close_at , Timex . now ( ) ) ->
36
+ { :error , { :bad_request , "New end date should occur after current time" } }
37
+
38
+ Timex . equal? ( previous_open_time , open_at ) or Timex . after? ( previous_open_time , Timex . now ( ) ) ->
39
+ update_assessment ( id , % { close_at: close_at , open_at: open_at } )
40
+
41
+ Timex . before? ( open_at , Timex . now ( ) ) ->
42
+ { :error , { :bad_request , "New Opening date should occur after current time" } }
43
+
44
+ true ->
45
+ { :error , { :forbidden , "Assessment is already opened" } }
46
+ end
47
+ else
48
+ { :error , { :forbidden , "User is not permitted to edit" } }
49
+ end
50
+ end
51
+
52
+ def toggle_publish_assessment ( _publisher = % User { role: role } , id , bool ) do
53
+ if role in @ publish_assessment_role do
54
+ update_assessment ( id , % { is_published: bool } )
55
+ else
56
+ { :error , { :forbidden , "User is not permitted to publish" } }
57
+ end
58
+ end
59
+
60
+ def delete_assessment ( _deleter = % User { role: role } , id ) do
61
+ if role in @ delete_assessment_role do
62
+ assessment = Repo . get ( Assessment , id )
63
+ Repo . delete ( assessment )
64
+ else
65
+ { :error , { :forbidden , "User is not permitted to delete" } }
66
+ end
67
+ end
68
+
24
69
@ spec user_total_xp ( % User { } ) :: integer ( )
25
70
def user_total_xp ( % User { id: user_id } ) when is_ecto_id ( user_id ) do
26
71
total_xp_bonus =
@@ -163,11 +208,18 @@ defmodule Cadet.Assessments do
163
208
164
209
def assessment_with_questions_and_answers ( id , user = % User { } , password )
165
210
when is_ecto_id ( id ) do
211
+ role = user . role
166
212
assessment =
167
- Assessment
168
- |> where ( id: ^ id )
169
- |> where ( is_published: true )
170
- |> Repo . one ( )
213
+ if role in @ open_all_assessment_roles do
214
+ Assessment
215
+ |> where ( id: ^ id )
216
+ |> Repo . one ( )
217
+ else
218
+ Assessment
219
+ |> where ( id: ^ id )
220
+ |> where ( is_published: true )
221
+ |> Repo . one ( )
222
+ end
171
223
172
224
if assessment do
173
225
assessment_with_questions_and_answers ( assessment , user , password )
@@ -206,11 +258,7 @@ defmodule Cadet.Assessments do
206
258
assessment_with_questions_and_answers ( id , user , nil )
207
259
end
208
260
209
- @ doc """
210
- Returns a list of assessments with all fields and an indicator showing whether it has been attempted
211
- by the supplied user
212
- """
213
- def all_published_assessments ( user = % User { } ) do
261
+ def all_assessments ( user = % User { } ) do
214
262
assessments =
215
263
Query . all_assessments_with_max_xp_and_grade ( )
216
264
|> subquery ( )
@@ -240,7 +288,7 @@ defmodule Cadet.Assessments do
240
288
question_count: q_count . count ,
241
289
graded_count: a_count . count
242
290
} )
243
- |> where ( is_published: true )
291
+ |> filter_published_assessments ( user )
244
292
|> order_by ( :open_at )
245
293
|> Repo . all ( )
246
294
|> Enum . map ( fn assessment = % Assessment { } ->
@@ -259,6 +307,14 @@ defmodule Cadet.Assessments do
259
307
{ :ok , assessments }
260
308
end
261
309
310
+ def filter_published_assessments ( assessments , user ) do
311
+ role = user . role
312
+ case role do
313
+ :student -> where ( assessments , is_published: true )
314
+ _ -> assessments
315
+ end
316
+ end
317
+
262
318
defp build_grading_status ( submission_status , a_type , q_count , g_count ) do
263
319
case a_type do
264
320
type when type in [ :mission , :sidequest ] ->
@@ -283,53 +339,108 @@ defmodule Cadet.Assessments do
283
339
@ doc """
284
340
The main function that inserts or updates assessments from the XML Parser
285
341
"""
286
- @ spec insert_or_update_assessments_and_questions ( map ( ) , [ map ( ) ] ) ::
342
+ @ spec insert_or_update_assessments_and_questions ( map ( ) , [ map ( ) ] , boolean ( ) ) ::
287
343
{ :ok , any ( ) }
288
344
| { :error , Ecto.Multi . name ( ) , any ( ) , % { optional ( Ecto.Multi . name ( ) ) => any ( ) } }
289
- def insert_or_update_assessments_and_questions ( assessment_params , questions_params ) do
345
+ def insert_or_update_assessments_and_questions ( assessment_params , questions_params , force_update ) do
290
346
assessment_multi =
291
347
Multi . insert_or_update (
292
348
Multi . new ( ) ,
293
349
:assessment ,
294
- insert_or_update_assessment_changeset ( assessment_params )
350
+ insert_or_update_assessment_changeset ( assessment_params , force_update )
295
351
)
296
352
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 ( )
353
+ if force_update and check_question_count ( assessment_multi , questions_params ) do
354
+ { :error , "Question count is different" }
355
+ else
356
+ questions_params
357
+ |> Enum . with_index ( 1 )
358
+ |> Enum . reduce ( assessment_multi , fn { question_params , index } , multi ->
359
+ Multi . run ( multi , String . to_atom ( "question#{ index } " ) , fn _repo ,
360
+ % { assessment: % Assessment { id: id } } ->
361
+ question_exists =
362
+ Repo . exists? ( where ( Question , [ q ] , q . assessment_id == ^ id and q . display_order == ^ index ) )
363
+ if ! force_update or ! question_exists do
364
+ question_params
365
+ |> Map . put ( :display_order , index )
366
+ |> build_question_changeset_for_assessment_id ( id )
367
+ |> Repo . insert ( )
368
+ else
369
+ params =
370
+ ( if ! question_params . max_xp do
371
+ question_params
372
+ |> Map . put ( :max_xp , 0 )
373
+ else
374
+ question_params
375
+ end )
376
+ |> Map . put ( :display_order , index )
377
+
378
+ % { id: question_id } =
379
+ where ( Question , [ q ] , q . display_order == ^ index and q . assessment_id == ^ id )
380
+ |> Repo . one ( )
381
+
382
+ changeset = Question . changeset ( % Question { assessment_id: id , id: question_id } , params )
383
+ Repo . update ( changeset )
384
+ end
385
+ end )
306
386
end )
307
- end )
308
- |> Repo . transaction ( )
387
+ |> Repo . transaction ( )
388
+ end
309
389
end
310
390
311
- @ spec insert_or_update_assessment_changeset ( map ( ) ) :: Ecto.Changeset . t ( )
312
- defp insert_or_update_assessment_changeset ( params = % { number: number } ) do
391
+ defp check_question_count ( assessment_multi , questions_params ) do
392
+ assessment_id =
393
+ ( assessment_multi . operations
394
+ |> List . first ( )
395
+ |> elem ( 1 )
396
+ |> elem ( 1 ) ) . data . id
397
+
398
+ if ! assessment_id do
399
+ false
400
+ else
401
+ existing_questions_count =
402
+ where ( Question , [ q ] , q . assessment_id == ^ assessment_id )
403
+ |> Repo . all ( )
404
+ |> Enum . count
405
+
406
+ new_questions_count = Enum . count ( questions_params )
407
+
408
+ existing_questions_count != new_questions_count
409
+ end
410
+ end
411
+
412
+ @ spec insert_or_update_assessment_changeset ( map ( ) , boolean ( ) ) :: Ecto.Changeset . t ( )
413
+ defp insert_or_update_assessment_changeset ( params = % { number: number } , force_update ) do
313
414
Assessment
314
415
|> where ( number: ^ number )
315
416
|> Repo . one ( )
316
417
|> case do
317
418
nil ->
318
419
Assessment . changeset ( % Assessment { } , params )
319
-
320
420
assessment ->
321
- if Timex . after? ( assessment . open_at , Timex . now ( ) ) do
322
- # Delete all existing questions
323
- % { id: assessment_id } = assessment
421
+ cond do
422
+ Timex . after? ( assessment . open_at , Timex . now ( ) ) ->
423
+ # Delete all existing questions
424
+ % { id: assessment_id } = assessment
324
425
325
- Question
326
- |> where ( assessment_id: ^ assessment_id )
327
- |> Repo . delete_all ( )
426
+ Question
427
+ |> where ( assessment_id: ^ assessment_id )
428
+ |> Repo . delete_all ( )
328
429
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" )
430
+ Assessment . changeset ( assessment , params )
431
+
432
+ force_update ->
433
+ # Maintain the same open/close date when force updating an assessment
434
+ new_params =
435
+ params
436
+ |> Map . delete ( :open_at )
437
+ |> Map . delete ( :close_at )
438
+
439
+ Assessment . changeset ( assessment , new_params )
440
+
441
+ true ->
442
+ # if the assessment is already open, don't mess with it
443
+ create_invalid_changeset_with_error ( :assessment , "is already open" )
333
444
end
334
445
end
335
446
end
0 commit comments