Skip to content

Commit d47fe74

Browse files
authored
Merge branch 'master' into dependabot/hex/ex_aws_s3-2.5.2
2 parents 7e7dc3f + 6586c6e commit d47fe74

File tree

150 files changed

+11019
-1253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+11019
-1253
lines changed

.credo.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
{Credo.Check.Readability.SpaceAfterCommas},
9595
{Credo.Check.Refactor.DoubleBooleanNegation},
9696
{Credo.Check.Refactor.CondStatements},
97-
{Credo.Check.Refactor.CyclomaticComplexity},
97+
{Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 10},
9898
{Credo.Check.Refactor.FunctionArity},
9999
{Credo.Check.Refactor.LongQuoteBlocks},
100100
{Credo.Check.Refactor.MatchInCondition},

.github/workflows/cd.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
- stable
66
- master
77
- deploy
8+
- deploy-stg
89
paths:
910
- "config/**"
1011
- "lib/**"
@@ -28,16 +29,16 @@ jobs:
2829
OTP_VERSION: 25.3.2
2930
steps:
3031
- uses: rlespinasse/[email protected]
31-
- uses: actions/checkout@v2
32+
- uses: actions/checkout@v4
3233
- name: Cache deps
33-
uses: actions/cache@v2
34+
uses: actions/cache@v4
3435
with:
3536
path: deps
3637
key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
3738
restore-keys: |
3839
${{ runner.os }}-mix-
3940
- name: Cache _build
40-
uses: actions/cache@v2
41+
uses: actions/cache@v4
4142
with:
4243
path: |
4344
_build

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ jobs:
3737
# needed because the postgres container does not provide a healthcheck
3838
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
3939
steps:
40-
- uses: actions/checkout@v2
40+
- uses: actions/checkout@v4
4141
- name: Cache deps
42-
uses: actions/cache@v2
42+
uses: actions/cache@v4
4343
with:
4444
path: deps
4545
key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
4646
restore-keys: |
4747
${{ runner.os }}-mix-
4848
- name: Cache _build
49-
uses: actions/cache@v2
49+
uses: actions/cache@v4
5050
with:
5151
path: |
5252
_build

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ erl_crash.dump
110110
# VSCode Elixir language support
111111
.elixir_ls/
112112

113+
# IDEs
114+
.idea
115+
.vscode
116+
113117
.env
114118

115119
# Generated lexer

.iex.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Ecto.Query
22
alias Cadet.Repo
3-
alias Cadet.Accounts.User
3+
alias Cadet.Accounts.{User, Team, TeamMember}
44
alias Cadet.Assessments.{Answer, Assessment, Question, Submission}
55
alias Cadet.Courses.Group

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ It is probably okay to use a different version of PostgreSQL or Erlang/OTP, but
6262
$ sudo service postgresql restart
6363
```
6464

65+
By default, the database is populated with 10 students and 5 assessments. Each student will have a submission to the corresponding submission. This can be changed in `priv/repo/seeds.exs` with the variables `number_of_students`, `number_of_assessments` and `number_of_questions`. Save the changes and run:
66+
67+
```bash
68+
$ mix ecto.reset
69+
```
70+
6571
4. Run the server on your local machine
6672

6773
```bash
@@ -141,7 +147,7 @@ Where there is a conflict between the two, the first one (lexmag) shall be the o
141147

142148
## Entity-Relationship Diagram
143149

144-
Generated with [DBeaver](https://dbeaver.io/) on 03 June 2022
150+
Generated with [DBeaver](https://dbeaver.io/) on 19 May 2024
145151

146152
![Entity-Relationship Diagram for cadet](schema.png)
147153

config/cadet.exs.example

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ config :cadet,
5656
# # You may need to write your own claim extractor for other providers
5757
# claim_extractor: Cadet.Auth.Providers.CognitoClaimExtractor
5858
# }},
59+
60+
# # Example SAML authentication with NUS Student IdP
61+
# "test_saml" =>
62+
# {Cadet.Auth.Providers.SAML,
63+
# %{
64+
# assertion_extractor: Cadet.Auth.Providers.NusstuAssertionExtractor,
65+
# client_redirect_url: "http://cadet.frontend:8000/login/callback"
66+
# }},
67+
5968
"test" =>
6069
{Cadet.Auth.Providers.Config,
6170
[
@@ -142,3 +151,29 @@ config :cadet,
142151
# You may also want to change the timezone used for scheduled jobs
143152
# config :cadet, Cadet.Jobs.Scheduler,
144153
# timezone: "Asia/Singapore",
154+
155+
# # Additional configuration for SAML authentication
156+
# # For more details, see https://github.com/handnot2/samly
157+
# config :samly, Samly.Provider,
158+
# idp_id_from: :path_segment,
159+
# service_providers: [
160+
# %{
161+
# id: "source-academy-backend",
162+
# certfile: "example_path/certfile.pem",
163+
# keyfile: "example_path/keyfile.pem"
164+
# }
165+
# ],
166+
# identity_providers: [
167+
# %{
168+
# id: "student",
169+
# sp_id: "source-academy-backend",
170+
# base_url: "https://example_backend/sso",
171+
# metadata_file: "student_idp_metadata.xml"
172+
# },
173+
# %{
174+
# id: "staff",
175+
# sp_id: "source-academy-backend",
176+
# base_url: "https://example_backend/sso",
177+
# metadata_file: "staff_idp_metadata.xml"
178+
# }
179+
# ]

config/dev.secrets.exs.example

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ config :cadet,
2929
# token_url: "https://github.com/login/oauth/access_token",
3030
# user_api: "https://api.github.com/user"
3131
# }},
32+
33+
# # Example SAML authentication with NUS Student IdP
34+
# "test_saml" =>
35+
# {Cadet.Auth.Providers.SAML,
36+
# %{
37+
# assertion_extractor: Cadet.Auth.Providers.NusstuAssertionExtractor,
38+
# client_redirect_url: "http://cadet.frontend:8000/login/callback"
39+
# }},
40+
3241
"test" =>
3342
{Cadet.Auth.Providers.Config,
3443
[
@@ -87,5 +96,39 @@ config :cadet,
8796
# ws_endpoint_address: "ws://hostname:port"
8897
]
8998

90-
config :sentry,
91-
dsn: "https://public_key/sentry.io/somethingsomething"
99+
config :openai,
100+
# find it at https://platform.openai.com/account/api-keys
101+
api_key: "the actual api key",
102+
# For source academy deployment, leave this as empty string.Ingeneral could find it at https://platform.openai.com/account/org-settings under "Organization ID".
103+
organization_key: "",
104+
# optional, passed to [HTTPoison.Request](https://hexdocs.pm/httpoison/HTTPoison.Request.html) options
105+
http_options: [recv_timeout: 170_0000]
106+
107+
# config :sentry,
108+
# dsn: "https://public_key/sentry.io/somethingsomething"
109+
110+
# # Additional configuration for SAML authentication
111+
# # For more details, see https://github.com/handnot2/samly
112+
# config :samly, Samly.Provider,
113+
# idp_id_from: :path_segment,
114+
# service_providers: [
115+
# %{
116+
# id: "source-academy-backend",
117+
# certfile: "example_path/certfile.pem",
118+
# keyfile: "example_path/keyfile.pem"
119+
# }
120+
# ],
121+
# identity_providers: [
122+
# %{
123+
# id: "student",
124+
# sp_id: "source-academy-backend",
125+
# base_url: "https://example_backend/sso",
126+
# metadata_file: "student_idp_metadata.xml"
127+
# },
128+
# %{
129+
# id: "staff",
130+
# sp_id: "source-academy-backend",
131+
# base_url: "https://example_backend/sso",
132+
# metadata_file: "staff_idp_metadata.xml"
133+
# }
134+
# ]

config/test.exs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ config :cadet,
6868
username: "E1234564"
6969
# role: :student
7070
}
71-
]}
71+
]},
72+
"saml" =>
73+
{Cadet.Auth.Providers.SAML,
74+
%{
75+
assertion_extractor: Cadet.Auth.Providers.NusstfAssertionExtractor,
76+
client_redirect_url: "https://cadet.frontend/login/callback"
77+
}}
7278
},
7379
autograder: [
7480
lambda_name: "dummy"

lib/cadet/accounts/notification_type.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import EctoEnum
33
defenum(Cadet.Accounts.NotificationType, :notification_type, [
44
# Notifications for new assessments
55
:new,
6-
# Notifications for autograded assessments
7-
:autograded,
8-
# Notifications for manually graded assessments
9-
:graded,
106
# Notifications for unsubmitted submissions
117
:unsubmitted,
128
# Notifications for submitted assessments
13-
:submitted
9+
:submitted,
10+
# Notifications for published grades
11+
:published_grading,
12+
# Notifications for unpublished grades
13+
:unpublished_grading
1414
])

lib/cadet/accounts/notifications.ex

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule Cadet.Accounts.Notifications do
88
import Ecto.Query
99

1010
alias Cadet.Repo
11-
alias Cadet.Accounts.{Notification, CourseRegistration, CourseRegistration}
11+
alias Cadet.Accounts.{Notification, CourseRegistration, CourseRegistration, Team, TeamMember}
1212
alias Cadet.Assessments.Submission
1313
alias Ecto.Multi
1414

@@ -145,43 +145,96 @@ defmodule Cadet.Accounts.Notifications do
145145
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
146146
def handle_unsubmit_notifications(assessment_id, student = %CourseRegistration{})
147147
when is_ecto_id(assessment_id) do
148-
# Fetch and delete all notifications of :autograded and :graded
148+
# Fetch and delete all notifications of :unpublished_grading
149149
# Add new notification :unsubmitted
150150

151151
Notification
152152
|> where(course_reg_id: ^student.id)
153153
|> where(assessment_id: ^assessment_id)
154-
|> where([n], n.type in ^[:autograded, :graded])
154+
|> where([n], n.type in ^[:unpublished_grading])
155155
|> Repo.delete_all()
156156

157157
write(%{
158158
type: :unsubmitted,
159-
role: student.role,
159+
role: :student,
160160
course_reg_id: student.id,
161161
assessment_id: assessment_id
162162
})
163163
end
164164

165165
@doc """
166-
Writes a notification that a student's submission has been
167-
graded successfully. (for the student)
166+
Function that handles notifications when a submission grade is unpublished.
167+
Deletes all :published notifications and adds a new :unpublished_grading notification.
168168
"""
169-
@spec write_notification_when_graded(integer(), any()) ::
169+
@spec handle_unpublish_grades_notifications(integer(), CourseRegistration.t()) ::
170170
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
171-
def write_notification_when_graded(submission_id, type) when type in [:graded, :autograded] do
172-
submission =
173-
Submission
174-
|> Repo.get_by(id: submission_id)
171+
def handle_unpublish_grades_notifications(assessment_id, student = %CourseRegistration{})
172+
when is_ecto_id(assessment_id) do
173+
Notification
174+
|> where(course_reg_id: ^student.id)
175+
|> where(assessment_id: ^assessment_id)
176+
|> where([n], n.type in ^[:published_grading])
177+
|> Repo.delete_all()
175178

176179
write(%{
177-
type: type,
180+
type: :unpublished_grading,
178181
read: false,
179182
role: :student,
180-
course_reg_id: submission.student_id,
181-
assessment_id: submission.assessment_id
183+
course_reg_id: student.id,
184+
assessment_id: assessment_id
182185
})
183186
end
184187

188+
@doc """
189+
Writes a notification that a student's submission has been
190+
graded successfully. (for the student)
191+
"""
192+
@spec write_notification_when_published(integer(), any()) ::
193+
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
194+
def write_notification_when_published(submission_id, type) when type in [:published_grading] do
195+
case Repo.get(Submission, submission_id) do
196+
nil ->
197+
{:error, %Ecto.Changeset{}}
198+
199+
submission ->
200+
case submission.student_id do
201+
nil ->
202+
team = Repo.get(Team, submission.team_id)
203+
204+
query =
205+
from(t in Team,
206+
join: tm in TeamMember,
207+
on: t.id == tm.team_id,
208+
join: cr in CourseRegistration,
209+
on: tm.student_id == cr.id,
210+
where: t.id == ^team.id,
211+
select: cr.id
212+
)
213+
214+
team_members = Repo.all(query)
215+
216+
Enum.each(team_members, fn tm_id ->
217+
write(%{
218+
type: type,
219+
read: false,
220+
role: :student,
221+
course_reg_id: tm_id,
222+
assessment_id: submission.assessment_id
223+
})
224+
end)
225+
226+
student_id ->
227+
write(%{
228+
type: type,
229+
read: false,
230+
role: :student,
231+
course_reg_id: student_id,
232+
assessment_id: submission.assessment_id
233+
})
234+
end
235+
end
236+
end
237+
185238
@doc """
186239
Writes a notification to all students that a new assessment is available.
187240
"""
@@ -223,7 +276,29 @@ defmodule Cadet.Accounts.Notifications do
223276
@spec write_notification_when_student_submits(Submission.t()) ::
224277
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
225278
def write_notification_when_student_submits(submission = %Submission{}) do
226-
avenger_id = get_avenger_id_of(submission.student_id)
279+
id =
280+
case submission.student_id do
281+
nil ->
282+
team_id = submission.team_id
283+
284+
team =
285+
Repo.one(
286+
from(t in Team,
287+
where: t.id == ^team_id,
288+
preload: [:team_members]
289+
)
290+
)
291+
292+
# Does not matter if team members have different Avengers
293+
# Just require one of them to be notified of the submission
294+
s_id = team.team_members |> hd() |> Map.get(:student_id)
295+
s_id
296+
297+
_ ->
298+
submission.student_id
299+
end
300+
301+
avenger_id = get_avenger_id_of(id)
227302

228303
if is_nil(avenger_id) do
229304
{:ok, nil}

0 commit comments

Comments
 (0)