Skip to content

Commit a5caf26

Browse files
committed
feat: self-sufficient grading client
1 parent 13164e6 commit a5caf26

File tree

8 files changed

+102
-53
lines changed

8 files changed

+102
-53
lines changed

modules/grading_client/config/config.exs

Lines changed: 0 additions & 23 deletions
This file was deleted.

modules/grading_client/lib/grading_client.ex

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,8 @@ defmodule GradingClient do
77
Checks the answer to a question.
88
"""
99

10-
@spec check_answer(any() | String.t(), integer(), integer()) :: :ok | {:error, String.t()}
11-
def check_answer(answer, module_id, question_id) when not is_binary(answer) do
12-
check_answer(module_id, question_id, "#{inspect(answer)}")
13-
end
14-
10+
@spec check_answer(any(), any(), any()) :: :correct | {:incorrect, String.t() | nil}
1511
def check_answer(answer, module_id, question_id) do
16-
# TODO: Make this configurable
17-
url = "http://localhost:4000/api/answers/check"
18-
19-
headers = [
20-
{"Content-Type", "application/json"}
21-
]
22-
23-
json = Jason.encode!(%{module_id: module_id, question_id: question_id, answer: answer})
24-
25-
%{body: body, status_code: 200} = HTTPoison.post!(url, json, headers)
26-
27-
%{"correct" => is_correct, "help_text" => help_text} = Jason.decode!(body)
28-
29-
if is_correct do
30-
:ok
31-
else
32-
{:error, help_text}
33-
end
12+
GradingClient.Answers.check(module_id, question_id, answer)
3413
end
3514
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
defmodule GradingClient.Answer do
2+
@enforce_keys [:question_id, :module_id, :answer, :help_text]
3+
defstruct [:question_id, :module_id, :answer, :help_text]
4+
5+
@type t :: %__MODULE__{
6+
module_id: term(),
7+
question_id: term(),
8+
answer: term(),
9+
help_text: term()
10+
}
11+
end
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
defmodule GradingClient.Answers do
2+
@moduledoc """
3+
This module is responsible for checking if an answer is correct.
4+
It uses the `AnswerStore` to fetch the answer and compares if it is correct or not.
5+
"""
6+
use GenServer
7+
8+
alias GradingClient.Answer
9+
10+
@table :answer_store
11+
12+
def start_link(opts) do
13+
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
14+
end
15+
16+
@impl true
17+
def init(opts) do
18+
:ets.new(@table, [:set, :named_table])
19+
20+
filename = opts[:filename]
21+
22+
{answers, _} = Code.eval_file(filename)
23+
24+
Enum.each(answers, fn answer ->
25+
:ets.insert(@table, {{answer.module_id, answer.question_id}, answer})
26+
end)
27+
28+
{:ok, nil}
29+
end
30+
31+
@doc """
32+
Checks if the given answer is correct.
33+
"""
34+
@spec check(integer(), integer(), String.t()) :: :correct | {:incorrect, String.t()}
35+
def check(module_id, question_id, answer) do
36+
GenServer.call(__MODULE__, {:check, module_id, question_id, answer})
37+
end
38+
39+
@impl true
40+
def handle_call({:check, module_id, question_id, answer}, _from, state) do
41+
result =
42+
case :ets.lookup(@table, {module_id, question_id}) do
43+
[] ->
44+
{:incorrect, "Question not found"}
45+
46+
[{_id, %Answer{answer: correct_answer, help_text: help_text}}] ->
47+
if answer == correct_answer do
48+
:correct
49+
else
50+
{:incorrect, help_text}
51+
end
52+
end
53+
54+
{:reply, result, state}
55+
end
56+
end

modules/grading_client/lib/grading_client/application.ex

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ defmodule GradingClient.Application do
22
use Application
33

44
def start(_type, _args) do
5-
Kino.SmartCell.register(GradingClient.GradingCell)
5+
Kino.SmartCell.register(GradingClient.GradedCell)
66

7-
children = []
7+
default_filename = Path.join(:code.priv_dir(:grading_client), "answers.exs")
8+
9+
children = [
10+
{GradingClient.Answers,
11+
filename: Application.get_env(:grading_client, :answers_file, default_filename)}
12+
]
813

914
opts = [strategy: :one_for_one, name: GradingClient.Supervisor]
1015
Supervisor.start_link(children, opts)

modules/grading_client/lib/grading_client/grading_cell.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
defmodule GradingClient.GradingCell do
1+
defmodule GradingClient.GradedCell do
22
use Kino.JS
33
use Kino.JS.Live
4-
use Kino.SmartCell, name: "Graded"
4+
use Kino.SmartCell, name: "Graded Cell"
55

66
@impl true
77
def init(attrs, ctx) do

modules/grading_client/mix.exs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ defmodule GradingClient.MixProject do
2222
# Run "mix help deps" to learn about dependencies.
2323
defp deps do
2424
[
25-
{:grading_server, path: "#{__DIR__}/../../grading_server"},
26-
{:httpoison, "~> 2.1"},
27-
{:jason, "~> 1.4"},
2825
{:kino, "~> 0.10"}
2926
]
3027
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
alias GradingClient.Answer
2+
3+
to_answers = fn module_name, answers ->
4+
Enum.map(answers, fn data ->
5+
%Answer{
6+
module_id: module_name,
7+
question_id: data.question_id,
8+
answer: data.answer,
9+
help_text: data[:help_text]
10+
}
11+
end)
12+
end
13+
14+
owasp_questions = [
15+
%{
16+
question_id: 1,
17+
answer: "A",
18+
help_text: "A"
19+
}
20+
]
21+
22+
List.flatten([
23+
to_answers.(OWASP, owasp_questions)
24+
])

0 commit comments

Comments
 (0)