Skip to content

Commit 58906d7

Browse files
authored
Merge branch 'master' into exam_mode
2 parents 434d21c + 65750b7 commit 58906d7

File tree

24 files changed

+310
-66
lines changed

24 files changed

+310
-66
lines changed

.credo.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
{Credo.Check.Readability.ModuleNames},
8484
{Credo.Check.Readability.ParenthesesOnZeroArityDefs},
8585
{Credo.Check.Readability.ParenthesesInCondition},
86-
{Credo.Check.Readability.PredicateFunctionNames},
86+
{Credo.Check.Readability.PredicateFunctionNames, exit_status: 0},
8787
{Credo.Check.Readability.PreferImplicitTry},
8888
{Credo.Check.Readability.RedundantBlankLines},
8989
{Credo.Check.Readability.StringSigils},

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ jobs:
2323
env:
2424
MIX_ENV: test
2525
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26-
ELIXIR_VERSION: 1.13.4
27-
OTP_VERSION: 25.3.2
26+
ELIXIR_VERSION: 1.18.3
27+
OTP_VERSION: 27.3.3
2828
services:
2929
postgres:
30-
image: postgres:14.2
30+
image: postgres:17.4
3131
env:
3232
POSTGRES_USER: postgres
3333
POSTGRES_PASSWORD: postgres

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ Cadet is the web application powering Source Academy.
1414

1515
### System requirements
1616

17-
1. Elixir 1.13.3+ (current version: 1.13.4)
18-
2. Erlang/OTP 23.2.1+ (current version: 25.3.2)
19-
3. PostgreSQL 12+ (tested to be working up to 14.5)
17+
1. Elixir 1.18+ (current version: 1.18.3)
18+
2. Erlang/OTP 27+ (current version: 27.3.3)
19+
3. PostgreSQL 12+ (tested to be working up to 17)
2020

2121
It is probably okay to use a different version of PostgreSQL or Erlang/OTP, but using a different version of Elixir may result in differences in e.g. `mix format`.
2222

config/config.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ config :cadet, Cadet.Jobs.Scheduler,
2525
# Compute rolling leaderboard every 2 hours
2626
{"0 */2 * * *", {Cadet.Assessments, :update_rolling_contest_leaderboards, []}},
2727
# Collate contest entries that close in the previous day at 00:01
28-
{"1 0 * * *", {Cadet.Assessments, :update_final_contest_entries, []}}
28+
{"1 0 * * *", {Cadet.Assessments, :update_final_contest_entries, []}},
29+
# Clean up expired exchange tokens at 00:01
30+
{"1 0 * * *", {Cadet.TokenExchange, :delete_expired, []}}
2931
]
3032

3133
# Configures the endpoint

config/dev.secrets.exs.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ config :cadet,
3636
# %{
3737
# assertion_extractor: Cadet.Auth.Providers.NusstuAssertionExtractor,
3838
# client_redirect_url: "http://cadet.frontend:8000/login/callback"
39+
# vscode_redirect_url_prefix: "vscode://source-academy.source-academy/sso",
40+
# client_post_exchange_redirect_url: "http://cadet.frontend:8000/login/vscode_callback",
3941
# }},
4042

4143
"test" =>

lib/cadet/assessments/assessments.ex

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ defmodule Cadet.Assessments do
941941
raw_answer,
942942
force_submit
943943
) do
944-
with {:ok, team} <- find_team(question.assessment.id, cr_id),
944+
with {:ok, _team} <- find_team(question.assessment.id, cr_id),
945945
{:ok, submission} <- find_or_create_submission(cr, question.assessment),
946946
{:status, true} <- {:status, force_submit or submission.status != :submitted},
947947
{:ok, _answer} <- insert_or_update_answer(submission, question, raw_answer, cr_id) do
@@ -1055,7 +1055,7 @@ defmodule Cadet.Assessments do
10551055

10561056
# Begin autograding job
10571057
GradingJob.force_grade_individual_submission(updated_submission)
1058-
update_xp_bonus(submission)
1058+
update_xp_bonus(updated_submission)
10591059

10601060
{:ok, nil}
10611061
else
@@ -1294,6 +1294,8 @@ defmodule Cadet.Assessments do
12941294
|> Submission.changeset(%{is_grading_published: true})
12951295
|> Repo.update()
12961296

1297+
update_xp_bonus(submission)
1298+
12971299
Notifications.write_notification_when_published(
12981300
submission.id,
12991301
:published_grading
@@ -1493,23 +1495,18 @@ defmodule Cadet.Assessments do
14931495
Answer
14941496
|> where(submission_id: ^submission_id)
14951497
|> order_by(:question_id)
1496-
|> group_by([a], a.id)
14971498
|> select([a], %{
1498-
# grouping by submission, so s.xp_bonus will be the same, but we need an
1499-
# aggregate function
1500-
total_xp: sum(a.xp) + sum(a.xp_adjustment)
1499+
total_xp: a.xp + a.xp_adjustment
15011500
})
15021501

15031502
total =
15041503
ans_xp
15051504
|> subquery
15061505
|> select([a], %{
1507-
total_xp: sum(a.total_xp)
1506+
total_xp: coalesce(sum(a.total_xp), 0)
15081507
})
15091508
|> Repo.one()
15101509

1511-
xp = decimal_to_integer(total.total_xp)
1512-
15131510
cur_time =
15141511
if submission.submitted_at == nil do
15151512
Timex.now()
@@ -1518,7 +1515,7 @@ defmodule Cadet.Assessments do
15181515
end
15191516

15201517
xp_bonus =
1521-
if xp <= 0 do
1518+
if total.total_xp <= 0 do
15221519
0
15231520
else
15241521
if Timex.before?(cur_time, Timex.shift(assessment.open_at, hours: early_hours)) do
@@ -2692,7 +2689,7 @@ defmodule Cadet.Assessments do
26922689

26932690
def has_last_modified_answer?(
26942691
question = %Question{},
2695-
cr = %CourseRegistration{id: cr_id},
2692+
cr = %CourseRegistration{id: _cr_id},
26962693
last_modified_at,
26972694
force_submit
26982695
) do
@@ -2703,15 +2700,6 @@ defmodule Cadet.Assessments do
27032700
else
27042701
{:status, _} ->
27052702
{:error, {:forbidden, "Assessment submission already finalised"}}
2706-
2707-
{:error, :race_condition} ->
2708-
{:error, {:internal_server_error, "Please try again later."}}
2709-
2710-
{:error, :invalid_vote} ->
2711-
{:error, {:bad_request, "Invalid vote! Vote is not saved."}}
2712-
2713-
_ ->
2714-
{:error, {:bad_request, "Missing or invalid parameter(s)"}}
27152703
end
27162704
end
27172705

lib/cadet/code_exchange.ex

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
defmodule Cadet.TokenExchange do
2+
@moduledoc """
3+
The TokenExchange entity stores short-lived codes to be exchanged for long-lived auth tokens.
4+
"""
5+
use Cadet, :model
6+
7+
import Ecto.Query
8+
9+
alias Cadet.Repo
10+
alias Cadet.Accounts.User
11+
12+
@primary_key {:code, :string, []}
13+
schema "token_exchange" do
14+
field(:generated_at, :utc_datetime_usec)
15+
field(:expires_at, :utc_datetime_usec)
16+
17+
belongs_to(:user, User)
18+
19+
timestamps()
20+
end
21+
22+
@required_fields ~w(code generated_at expires_at user_id)a
23+
24+
def get_by_code(code) do
25+
case Repo.get_by(__MODULE__, code: code) do
26+
nil ->
27+
{:error, "Not found"}
28+
29+
struct ->
30+
if Timex.before?(struct.expires_at, Timex.now()) do
31+
{:error, "Expired"}
32+
else
33+
struct = Repo.preload(struct, :user)
34+
Repo.delete(struct)
35+
{:ok, struct}
36+
end
37+
end
38+
end
39+
40+
def delete_expired do
41+
now = Timex.now()
42+
43+
Repo.delete_all(from(c in __MODULE__, where: c.expires_at < ^now))
44+
end
45+
46+
def changeset(struct, attrs) do
47+
struct
48+
|> cast(attrs, @required_fields)
49+
|> validate_required(@required_fields)
50+
end
51+
52+
def insert(attrs) do
53+
changeset =
54+
%__MODULE__{}
55+
|> changeset(attrs)
56+
57+
changeset
58+
|> Repo.insert()
59+
end
60+
end

lib/cadet/devices/devices.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ defmodule Cadet.Devices do
7272
with {:ok, device} <- maybe_insert_device(type, secret),
7373
{:ok, registration} <-
7474
%DeviceRegistration{}
75-
|> DeviceRegistration.changeset(%{user_id: user_id, device_id: device.id, title: title})
75+
|> DeviceRegistration.changeset(%{
76+
user_id: user_id,
77+
device_id: device.id,
78+
title: title
79+
})
7680
|> Repo.insert() do
7781
{:ok, registration |> Repo.preload(:device)}
7882
end

lib/cadet_web/admin_controllers/admin_assets_controller.ex

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ defmodule CadetWeb.AdminAssetsController do
2222

2323
case Assets.delete_object(Courses.assets_prefix(course_reg.course), foldername, filename) do
2424
{:error, {status, message}} -> conn |> put_status(status) |> text(message)
25-
_ -> conn |> put_status(204) |> text('')
25+
_ -> conn |> put_status(204) |> text("")
2626
end
2727
end
2828

29+
# Ignore the dialyzer warning, just ctrl click the
30+
# `Assets.upload_to_s3` function to see the type,
31+
# it clearly returns a string URL
32+
@dialyzer {:no_match, upload: 2}
33+
2934
def upload(conn, %{
3035
"upload" => upload_params,
3136
"filename" => filename,
@@ -96,7 +101,9 @@ defmodule CadetWeb.AdminAssetsController do
96101
parameters do
97102
folderName(:path, :string, "Folder name", required: true)
98103

99-
fileName(:path, :string, "File path in folder, which may contain subfolders", required: true)
104+
fileName(:path, :string, "File path in folder, which may contain subfolders",
105+
required: true
106+
)
100107
end
101108

102109
security([%{JWT: []}])
@@ -115,7 +122,9 @@ defmodule CadetWeb.AdminAssetsController do
115122
parameters do
116123
folderName(:path, :string, "Folder name", required: true)
117124

118-
fileName(:path, :string, "File path in folder, which may contain subfolders", required: true)
125+
fileName(:path, :string, "File path in folder, which may contain subfolders",
126+
required: true
127+
)
119128
end
120129

121130
security([%{JWT: []}])

lib/cadet_web/admin_controllers/admin_grading_controller.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,9 @@ defmodule CadetWeb.AdminGradingController do
378378
required: true
379379
)
380380

381-
student(Schema.ref(:StudentInfo), "Student who created the submission", required: true)
381+
student(Schema.ref(:StudentInfo), "Student who created the submission",
382+
required: true
383+
)
382384

383385
unsubmittedBy(Schema.ref(:GraderInfo))
384386
unsubmittedAt(:string, "Last unsubmitted at", format: "date-time", required: false)

0 commit comments

Comments
 (0)