Skip to content

CP3108 Game Component - Backend Modification #581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
9c9702c
Update user.ex
Cheng20010201 Mar 27, 2020
82605a9
Add files via upload
Cheng20010201 Mar 27, 2020
2acbf8a
Update user_controller.ex
Cheng20010201 Mar 27, 2020
faca0ca
Update router.ex
Cheng20010201 Mar 27, 2020
bed98a9
Update user_view.ex
Cheng20010201 Mar 27, 2020
495a915
Create collectibles.ex
Cheng20010201 Mar 27, 2020
517dca7
Update collectibles.ex
Cheng20010201 Mar 29, 2020
bcf34a9
Update router.ex
Cheng20010201 Mar 29, 2020
b22dc98
Update user_controller.ex
Cheng20010201 Mar 29, 2020
d2fc579
Update collectibles.ex
Cheng20010201 Mar 30, 2020
709e02a
Add a game_states column to the database.
Cheng20010201 Apr 2, 2020
d052ce4
Delete original collectibles.
Cheng20010201 Apr 2, 2020
16323c9
added trivial change to maitain consitency
Cheng20010201 Apr 2, 2020
18375b5
changed some post actions to put actions
Cheng20010201 Apr 2, 2020
9635a2b
updated to game_states version
Cheng20010201 Apr 2, 2020
e92b5c0
added one line to be consistent with migration
Cheng20010201 Apr 2, 2020
210cff6
make changes to model the behavior of game_states
Cheng20010201 Apr 2, 2020
431a058
Added GroundControl
Wong-ZZ Apr 6, 2020
5a865d9
Minor edit
Wong-ZZ Apr 7, 2020
0218305
Added few functions regarding game data
Cheng20010201 Apr 10, 2020
14f5025
Added some routes regarding game states.
Cheng20010201 Apr 10, 2020
5bb40e9
Added some functios regarding game states.
Cheng20010201 Apr 10, 2020
26ac37c
Update README.md (#570)
jiachen247 Jan 29, 2020
2053868
Update to allow for xml files
athuyaoo Apr 1, 2020
ac472d3
Create add_game_states
Cheng20010201 Apr 10, 2020
39791c3
Update router.ex
Cheng20010201 Apr 10, 2020
6b42190
Update user_controller.ex
Cheng20010201 Apr 10, 2020
3f2bcb3
Update game_states.ex
Cheng20010201 Apr 10, 2020
71f1ac9
fixed a consistency problem
Cheng20010201 Apr 10, 2020
fbdf592
Update user_controller.ex
Cheng20010201 Apr 10, 2020
4ca0ffb
Merge branch 'sa_2021' into master
Cheng20010201 Apr 13, 2020
c0c73c9
Changed variable naming
Wong-ZZ Apr 15, 2020
e3eddd6
Fixed bug when force updating assessments that are not opened. Added …
Wong-ZZ Apr 15, 2020
72d9744
Delete 20200402094115_add_user_game_states.exs
Cheng20010201 Apr 15, 2020
9d482c1
Delete add_game_states
Cheng20010201 Apr 15, 2020
53141ea
Add files via upload
Cheng20010201 Apr 15, 2020
8c956cf
Update user_view.ex
Cheng20010201 Apr 15, 2020
34c4567
Fixed no status codes error
Cheng20010201 Apr 15, 2020
0f59ab0
Solved no status codes error
Cheng20010201 Apr 15, 2020
efbc305
Update game_states.ex
Cheng20010201 Apr 16, 2020
3d5e37d
Added status codes information
Cheng20010201 Apr 16, 2020
3ca9dc6
Added status codes information
Cheng20010201 Apr 16, 2020
3c3c780
Add function to differentiate students from others
Cheng20010201 Apr 16, 2020
b39ad61
Added swagger paths
Wong-ZZ Apr 19, 2020
1a07757
Fixed format
Wong-ZZ Apr 19, 2020
7663bf2
Modified AssessmentsController tests to fit with changes to how the i…
Wong-ZZ Apr 19, 2020
159a2d3
Updated XML parsers tests
Wong-ZZ Apr 20, 2020
c7f01c2
Changed assessment deletion such that associated submissions are also…
Wong-ZZ Apr 20, 2020
5d4e6d8
Minor formatting change
Wong-ZZ Apr 20, 2020
ae7beac
Updated AssessmentOverview schema
Wong-ZZ Apr 20, 2020
0a4cdb3
Merge branch 'ground_control' into mergeGroundControl
athuyaoo Apr 21, 2020
6597a3e
Ran Mix Format
athuyaoo Apr 21, 2020
45448ba
Modified user_controller_test.exs
Cheng20010201 Apr 22, 2020
a47cbab
Update accounts_test.exs
Cheng20010201 Apr 22, 2020
3ff9e52
Minor change to user.ex
Cheng20010201 Apr 22, 2020
cca29ea
Eliminated one compile warning
Cheng20010201 Apr 22, 2020
0d59384
Added tests on collectible system
Cheng20010201 Apr 22, 2020
c34784c
Merge pull request #2 from Source-Academy-Game/mergeGroundControl
Cheng20010201 Apr 22, 2020
9e7f141
Modified Module Name
Cheng20010201 Apr 23, 2020
1525c25
Some format change to adhere to backend style
Cheng20010201 Apr 23, 2020
b71612a
fix issues proposed by reviewer
Cheng20010201 Apr 29, 2020
f2e3b3f
fix issues proposed
Cheng20010201 Apr 29, 2020
a46af78
modified positioning of test cases
Cheng20010201 Apr 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions lib/cadet/accounts/game_states.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule Cadet.Accounts.GameStates do
use Cadet, :context
alias Cadet.Accounts.User

@update_gamestate_roles ~w(student)a
# currently in this module no error handling function
# has been implemented yet

def user_game_states(user) do
user.game_states
end

def user_collectibles(user) do
user.game_states["collectibles"]
end

def user_save_data(user) do
user.game_states["completed_quests"]
end

def update(user = %User{role: role}, new_game_states) do
if role in @update_gamestate_roles do
changeset = cast(user, %{game_states: new_game_states}, [:game_states])
Repo.update!(changeset)
{:ok, nil}
else
{:error, {:forbidden, "Please try again later."}}
end
end

def clear(user = %User{role: role}) do
if role in @update_gamestate_roles do
changeset =
cast(user, %{game_states: %{collectibles: %{}, completed_quests: []}}, [
:game_states
])

Repo.update!(changeset)
{:ok, nil}
else
{:error, {:forbidden, "Please try again later."}}
end
end
end
1 change: 1 addition & 0 deletions lib/cadet/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Cadet.Accounts.User do
field(:name, :string)
field(:role, Role)
field(:nusnet_id, :string)
field(:game_states, :map, default: %{"collectibles" => %{}, "completed_quests" => []})
belongs_to(:group, Group)
timestamps()
end
Expand Down
212 changes: 180 additions & 32 deletions lib/cadet/assessments/assessments.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,62 @@ defmodule Cadet.Assessments do
@xp_early_submission_max_bonus 100
@xp_bonus_assessment_type ~w(mission sidequest)a
@submit_answer_roles ~w(student)a
@change_dates_assessment_role ~w(staff admin)a
@delete_assessment_role ~w(staff admin)a
@publish_assessment_role ~w(staff admin)a
@unsubmit_assessment_role ~w(staff admin)a
@grading_roles ~w()a
@see_all_submissions_roles ~w(staff admin)a
@open_all_assessment_roles ~w(staff admin)a

def change_dates_assessment(_user = %User{role: role}, id, close_at, open_at) do
if role in @change_dates_assessment_role do
assessment = Repo.get(Assessment, id)
previous_open_time = assessment.open_at

cond do
Timex.before?(close_at, open_at) ->
{:error, {:bad_request, "New end date should occur after new opening date"}}

Timex.before?(close_at, Timex.now()) ->
{:error, {:bad_request, "New end date should occur after current time"}}

Timex.equal?(previous_open_time, open_at) or Timex.after?(previous_open_time, Timex.now()) ->
update_assessment(id, %{close_at: close_at, open_at: open_at})

Timex.before?(open_at, Timex.now()) ->
{:error, {:bad_request, "New Opening date should occur after current time"}}

true ->
{:error, {:unauthorized, "Assessment is already opened"}}
end
else
{:error, {:forbidden, "User is not permitted to edit"}}
end
end

def toggle_publish_assessment(_publisher = %User{role: role}, id, toggle_publish_to) do
if role in @publish_assessment_role do
update_assessment(id, %{is_published: toggle_publish_to})
else
{:error, {:forbidden, "User is not permitted to publish"}}
end
end

def delete_assessment(_deleter = %User{role: role}, id) do
if role in @delete_assessment_role do
assessment = Repo.get(Assessment, id)

Submission
|> where(assessment_id: ^id)
|> Repo.delete_all()

Repo.delete(assessment)
else
{:error, {:forbidden, "User is not permitted to delete"}}
end
end

@spec user_total_xp(%User{}) :: integer()
def user_total_xp(%User{id: user_id}) when is_ecto_id(user_id) do
total_xp_bonus =
Expand Down Expand Up @@ -163,11 +214,19 @@ defmodule Cadet.Assessments do

def assessment_with_questions_and_answers(id, user = %User{}, password)
when is_ecto_id(id) do
role = user.role

assessment =
Assessment
|> where(id: ^id)
|> where(is_published: true)
|> Repo.one()
if role in @open_all_assessment_roles do
Assessment
|> where(id: ^id)
|> Repo.one()
else
Assessment
|> where(id: ^id)
|> where(is_published: true)
|> Repo.one()
end

if assessment do
assessment_with_questions_and_answers(assessment, user, password)
Expand Down Expand Up @@ -210,7 +269,7 @@ defmodule Cadet.Assessments do
Returns a list of assessments with all fields and an indicator showing whether it has been attempted
by the supplied user
"""
def all_published_assessments(user = %User{}) do
def all_assessments(user = %User{}) do
assessments =
Query.all_assessments_with_max_xp_and_grade()
|> subquery()
Expand Down Expand Up @@ -240,7 +299,7 @@ defmodule Cadet.Assessments do
question_count: q_count.count,
graded_count: a_count.count
})
|> where(is_published: true)
|> filter_published_assessments(user)
|> order_by(:open_at)
|> Repo.all()
|> Enum.map(fn assessment = %Assessment{} ->
Expand All @@ -259,6 +318,15 @@ defmodule Cadet.Assessments do
{:ok, assessments}
end

def filter_published_assessments(assessments, user) do
role = user.role

case role do
:student -> where(assessments, is_published: true)
_ -> assessments
end
end

defp build_grading_status(submission_status, a_type, q_count, g_count) do
case a_type do
type when type in [:mission, :sidequest] ->
Expand All @@ -283,33 +351,101 @@ defmodule Cadet.Assessments do
@doc """
The main function that inserts or updates assessments from the XML Parser
"""
@spec insert_or_update_assessments_and_questions(map(), [map()]) ::
@spec insert_or_update_assessments_and_questions(map(), [map()], boolean()) ::
{:ok, any()}
| {:error, Ecto.Multi.name(), any(), %{optional(Ecto.Multi.name()) => any()}}
def insert_or_update_assessments_and_questions(assessment_params, questions_params) do
def insert_or_update_assessments_and_questions(
assessment_params,
questions_params,
force_update
) do
assessment_multi =
Multi.insert_or_update(
Multi.new(),
:assessment,
insert_or_update_assessment_changeset(assessment_params)
insert_or_update_assessment_changeset(assessment_params, force_update)
)

questions_params
|> Enum.with_index(1)
|> Enum.reduce(assessment_multi, fn {question_params, index}, multi ->
Multi.run(multi, String.to_atom("question#{index}"), fn _repo,
%{assessment: %Assessment{id: id}} ->
question_params
|> Map.put(:display_order, index)
|> build_question_changeset_for_assessment_id(id)
|> Repo.insert()
if force_update and invalid_force_update(assessment_multi, questions_params) do
{:error, "Question count is different"}
else
questions_params
|> Enum.with_index(1)
|> Enum.reduce(assessment_multi, fn {question_params, index}, multi ->
Multi.run(multi, String.to_atom("question#{index}"), fn _repo,
%{assessment: %Assessment{id: id}} ->
question_exists =
Repo.exists?(
where(Question, [q], q.assessment_id == ^id and q.display_order == ^index)
)

# the !question_exists check allows for force updating of brand new assessments
if !force_update or !question_exists do
question_params
|> Map.put(:display_order, index)
|> build_question_changeset_for_assessment_id(id)
|> Repo.insert()
else
params =
question_params
|> Map.put_new(:max_xp, 0)
|> Map.put(:display_order, index)

%{id: question_id, type: type} =
Question
|> where([q], q.display_order == ^index and q.assessment_id == ^id)
|> Repo.one()

if question_params.type != Atom.to_string(type) do
{:error,
create_invalid_changeset_with_error(
:question,
"Question types should remain the same"
)}
else
changeset =
Question.changeset(%Question{assessment_id: id, id: question_id}, params)

Repo.update(changeset)
end
end
end)
end)
end)
|> Repo.transaction()
|> Repo.transaction()
end
end

@spec insert_or_update_assessment_changeset(map()) :: Ecto.Changeset.t()
defp insert_or_update_assessment_changeset(params = %{number: number}) do
# Function that checks if the force update is invalid. The force update is only invalid
# if the new question count is different from the old question count.
defp invalid_force_update(assessment_multi, questions_params) do
assessment_id =
(assessment_multi.operations
|> List.first()
|> elem(1)
|> elem(1)).data.id

if assessment_id do
open_date = Repo.get(Assessment, assessment_id).open_at
# check if assessment is already opened
if Timex.after?(open_date, Timex.now()) do
false
else
existing_questions_count =
Question
|> where([q], q.assessment_id == ^assessment_id)
|> Repo.all()
|> Enum.count()

new_questions_count = Enum.count(questions_params)
existing_questions_count != new_questions_count
end
else
false
end
end

@spec insert_or_update_assessment_changeset(map(), boolean()) :: Ecto.Changeset.t()
defp insert_or_update_assessment_changeset(params = %{number: number}, force_update) do
Assessment
|> where(number: ^number)
|> Repo.one()
Expand All @@ -318,18 +454,30 @@ defmodule Cadet.Assessments do
Assessment.changeset(%Assessment{}, params)

assessment ->
if Timex.after?(assessment.open_at, Timex.now()) do
# Delete all existing questions
%{id: assessment_id} = assessment
cond do
Timex.after?(assessment.open_at, Timex.now()) ->
# Delete all existing questions
%{id: assessment_id} = assessment

Question
|> where(assessment_id: ^assessment_id)
|> Repo.delete_all()
Question
|> where(assessment_id: ^assessment_id)
|> Repo.delete_all()

Assessment.changeset(assessment, params)
else
# if the assessment is already open, don't mess with it
create_invalid_changeset_with_error(:assessment, "is already open")
Assessment.changeset(assessment, params)

force_update ->
# Maintain the same open/close date when force updating an assessment
new_params =
params
|> Map.delete(:open_at)
|> Map.delete(:close_at)
|> Map.delete(:is_published)

Assessment.changeset(assessment, new_params)

true ->
# if the assessment is already open, don't mess with it
create_invalid_changeset_with_error(:assessment, "is already open")
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/cadet/course/materialUpload.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Cadet.Course.MaterialUpload do
use Arc.Definition
use Arc.Ecto.Definition

@extension_whitelist ~w(.doc .docx .jpg .pdf .png .ppt .pptx .txt .xls .xlsx)
@extension_whitelist ~w(.doc .docx .jpg .pdf .png .ppt .pptx .txt .xls .xlsx .xml)
@versions [:original]

def bucket, do: :cadet |> Application.fetch_env!(:uploader) |> Keyword.get(:materials_bucket)
Expand Down
Loading