• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / backend / e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

31 Mar 2025 09:12AM UTC coverage: 19.982% (-73.6%) from 93.607%
e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

Pull #1236

github

RichDom2185
Redate migrations to maintain total ordering
Pull Request #1236: Added Exam mode

12 of 57 new or added lines in 8 files covered. (21.05%)

2430 existing lines in 97 files now uncovered.

671 of 3358 relevant lines covered (19.98%)

3.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

15.19
/lib/cadet_web/admin_controllers/admin_user_controller.ex
1
defmodule CadetWeb.AdminUserController do
2
  use CadetWeb, :controller
3
  use PhoenixSwagger
4

5
  import Ecto.Query
6
  alias Cadet.Repo
7
  alias Cadet.{Accounts, Assessments, Courses}
8
  alias Cadet.Accounts.{CourseRegistrations, CourseRegistration, Role}
9

10
  # This controller is used to find all users of a course
11

12
  def index(conn, filter) do
UNCOV
13
    users =
×
UNCOV
14
      filter |> try_keywordise_string_keys() |> Accounts.get_users_by(conn.assigns.course_reg)
×
15

NEW
16
    render(conn, "users.json", users: users, is_admin: false)
×
17
  end
18

19
  def combined_total_xp(conn, %{"course_reg_id" => course_reg_id}) do
UNCOV
20
    course_reg = Repo.get(CourseRegistration, course_reg_id)
×
21

UNCOV
22
    course_id = course_reg.course_id
×
UNCOV
23
    user_id = course_reg.user_id
×
UNCOV
24
    course_reg_id = course_reg.id
×
25

UNCOV
26
    total_xp = Assessments.user_total_xp(course_id, user_id, course_reg_id)
×
UNCOV
27
    json(conn, %{totalXp: total_xp})
×
28
  end
29

30
  @add_users_role ~w(admin)a
31
  def get_students(conn, filter) do
32
    users =
×
33
      filter |> try_keywordise_string_keys() |> Accounts.get_users_by(conn.assigns.course_reg)
×
34

35
    render(conn, "get_students.json", users: users)
×
36
  end
37

38
  @add_users_role ~w(admin)a
39
  def upsert_users_and_groups(conn, %{
40
        "course_id" => course_id,
41
        "users" => usernames_roles_groups,
42
        "provider" => provider
43
      }) do
UNCOV
44
    %{role: admin_role} = conn.assigns.course_reg
×
UNCOV
45
    usernames_roles_groups = usernames_roles_groups |> Enum.map(&to_snake_case_atom_keys/1)
×
46

UNCOV
47
    with {:validate_cap, true} <-
×
48
           {:validate_cap,
49
            Enum.count(CourseRegistrations.get_users(course_id) ++ usernames_roles_groups) <= 1500},
UNCOV
50
         {:validate_role, true} <- {:validate_role, admin_role in @add_users_role},
×
UNCOV
51
         {:validate_provider, true} <-
×
52
           {:validate_provider,
53
            Map.has_key?(Application.get_env(:cadet, :identity_providers, %{}), provider)},
UNCOV
54
         {:validate_usernames, true} <-
×
55
           {:validate_usernames,
56
            Enum.all?(usernames_roles_groups, fn x ->
UNCOV
57
              Map.has_key?(x, :username) and is_binary(x.username) and x.username != ""
×
58
            end)},
UNCOV
59
         {:validate_roles, true} <-
×
60
           {:validate_roles,
61
            Enum.all?(usernames_roles_groups, fn x ->
UNCOV
62
              Map.has_key?(x, :role) and String.to_atom(x.role) in Role.__enums__()
×
63
            end)} do
UNCOV
64
      {:ok, conn} =
×
65
        Repo.transaction(
66
          fn ->
UNCOV
67
            with {:upsert_users, :ok} <-
×
68
                   {:upsert_users,
69
                    CourseRegistrations.upsert_users_in_course(
70
                      provider,
71
                      usernames_roles_groups,
72
                      course_id
73
                    )},
UNCOV
74
                 {:upsert_groups, :ok} <-
×
75
                   {:upsert_groups,
76
                    Courses.upsert_groups_in_course(usernames_roles_groups, course_id, provider)} do
UNCOV
77
              text(conn, "OK")
×
78
            else
79
              {:upsert_users, {:error, {status, message}}} ->
80
                conn |> put_status(status) |> text(message)
×
81

82
              {:upsert_groups, {:error, {status, message}}} ->
83
                conn |> put_status(status) |> text(message)
×
84
            end
85
          end,
86
          timeout: 20_000
87
        )
88

UNCOV
89
      conn
×
90
    else
91
      {:validate_cap, false} ->
UNCOV
92
        conn |> put_status(:bad_request) |> text("A course can have maximum of 1500 users")
×
93

94
      {:validate_role, false} ->
UNCOV
95
        conn |> put_status(:forbidden) |> text("User is not permitted to add users")
×
96

97
      {:validate_provider, false} ->
UNCOV
98
        conn |> put_status(:bad_request) |> text("Invalid authentication provider")
×
99

100
      {:validate_usernames, false} ->
UNCOV
101
        conn |> put_status(:bad_request) |> text("Invalid username(s) provided")
×
102

103
      {:validate_roles, false} ->
UNCOV
104
        conn |> put_status(:bad_request) |> text("Invalid role(s) provided")
×
105
    end
106
  end
107

108
  @update_role_roles ~w(admin)a
109
  def update_role(conn, %{"role" => role, "course_reg_id" => course_reg_id}) do
UNCOV
110
    course_reg_id = course_reg_id |> String.to_integer()
×
111

UNCOV
112
    %{id: admin_course_reg_id, role: admin_role, course_id: admin_course_id} =
×
UNCOV
113
      conn.assigns.course_reg
×
114

UNCOV
115
    with {:validate_role, true} <- {:validate_role, admin_role in @update_role_roles},
×
UNCOV
116
         {:validate_not_self, true} <- {:validate_not_self, admin_course_reg_id != course_reg_id},
×
UNCOV
117
         {:get_cr, user_course_reg} when not is_nil(user_course_reg) <-
×
UNCOV
118
           {:get_cr, CourseRegistration |> where(id: ^course_reg_id) |> Repo.one()},
×
UNCOV
119
         {:validate_same_course, true} <-
×
UNCOV
120
           {:validate_same_course, user_course_reg.course_id == admin_course_id} do
×
UNCOV
121
      case CourseRegistrations.update_role(role, course_reg_id) do
×
122
        {:ok, %{}} ->
UNCOV
123
          text(conn, "OK")
×
124

125
        {:error, {status, message}} ->
126
          conn
127
          |> put_status(status)
UNCOV
128
          |> text(message)
×
129
      end
130
    else
131
      {:validate_role, false} ->
132
        conn |> put_status(:forbidden) |> text("User is not permitted to change others' roles")
×
133

134
      {:validate_not_self, false} ->
135
        conn |> put_status(:bad_request) |> text("Admin not allowed to downgrade own role")
×
136

137
      {:get_cr, _} ->
UNCOV
138
        conn |> put_status(:bad_request) |> text("User course registration does not exist")
×
139

140
      {:validate_same_course, false} ->
UNCOV
141
        conn |> put_status(:forbidden) |> text("User is in a different course")
×
142
    end
143
  end
144

145
  @delete_user_roles ~w(admin)a
146
  def delete_user(conn, %{"course_reg_id" => course_reg_id}) do
UNCOV
147
    course_reg_id = course_reg_id |> String.to_integer()
×
148

UNCOV
149
    %{id: admin_course_reg_id, role: admin_role, course_id: admin_course_id} =
×
UNCOV
150
      conn.assigns.course_reg
×
151

UNCOV
152
    with {:validate_role, true} <- {:validate_role, admin_role in @delete_user_roles},
×
UNCOV
153
         {:validate_not_self, true} <- {:validate_not_self, admin_course_reg_id != course_reg_id},
×
UNCOV
154
         {:get_cr, user_course_reg} when not is_nil(user_course_reg) <-
×
UNCOV
155
           {:get_cr, CourseRegistration |> where(id: ^course_reg_id) |> Repo.one()},
×
UNCOV
156
         {:prevent_delete_admin, true} <- {:prevent_delete_admin, user_course_reg.role != :admin},
×
UNCOV
157
         {:validate_same_course, true} <-
×
UNCOV
158
           {:validate_same_course, user_course_reg.course_id == admin_course_id} do
×
UNCOV
159
      case CourseRegistrations.delete_course_registration(course_reg_id) do
×
160
        {:ok, %{}} ->
UNCOV
161
          text(conn, "OK")
×
162

163
        {:error, {status, message}} ->
164
          conn
165
          |> put_status(status)
166
          |> text(message)
×
167
      end
168
    else
169
      {:validate_role, false} ->
170
        conn |> put_status(:forbidden) |> text("User is not permitted to delete other users")
×
171

172
      {:validate_not_self, false} ->
173
        conn
174
        |> put_status(:bad_request)
UNCOV
175
        |> text("Admin not allowed to delete ownself from course")
×
176

177
      {:get_cr, _} ->
UNCOV
178
        conn |> put_status(:bad_request) |> text("User course registration does not exist")
×
179

180
      {:prevent_delete_admin, false} ->
UNCOV
181
        conn |> put_status(:bad_request) |> text("Admins cannot be deleted")
×
182

183
      {:validate_same_course, false} ->
UNCOV
184
        conn |> put_status(:forbidden) |> text("User is in a different course")
×
185
    end
186
  end
187

188
  swagger_path :index do
1✔
189
    get("/courses/{course_id}/admin/users")
190

191
    summary("Returns a list of users in the course owned by the admin")
192

193
    security([%{JWT: []}])
194
    produces("application/json")
195
    response(200, "OK", Schema.ref(:AdminUserInfo))
196
    response(401, "Unauthorised")
197
  end
198

199
  swagger_path :combined_total_xp do
1✔
200
    get("/courses/{course_id}/admin/users/{course_reg_id}/total_xp")
201

202
    summary("Get the specified user's total XP from achievements and assessments")
203

204
    security([%{JWT: []}])
205
    produces("application/json")
206

207
    parameters do
208
      course_id(:path, :integer, "Course ID", required: true)
209
      course_reg_id(:path, :integer, "Course registration ID", required: true)
210
    end
211

212
    response(200, "OK", Schema.ref(:TotalXPInfo))
213
    response(401, "Unauthorised")
214
  end
215

216
  swagger_path :upsert_users_and_groups do
1✔
217
    put("/courses/{course_id}/admin/users")
218

219
    summary("Adds the list of usernames and roles to the course")
220
    security([%{JWT: []}])
221
    consumes("application/json")
222

223
    parameters do
224
      course_id(:path, :integer, "Course ID", required: true)
225
      users(:body, Schema.array(:UsernameAndRole), "Array of usernames and roles", required: true)
226

227
      provider(:body, :string, "The authentication provider linked to these usernames",
228
        required: true
229
      )
230
    end
231

232
    response(200, "OK")
233
    response(400, "Bad Request. Invalid provider, username or role")
234
    response(403, "Forbidden. You are not an admin")
235
  end
236

237
  swagger_path :update_role do
1✔
238
    put("/courses/{course_id}/admin/users/{course_reg_id}/role")
239

240
    summary("Updates the role of the given user in the the course")
241
    security([%{JWT: []}])
242
    consumes("application/json")
243

244
    parameters do
245
      course_id(:path, :integer, "Course ID", required: true)
246
      role(:body, :role, "The new role", required: true)
247

248
      courseRegId(
249
        :body,
250
        :integer,
251
        "The course registration of the user whose role is to be updated",
252
        required: true
253
      )
254
    end
255

256
    response(200, "OK")
257

258
    response(
259
      400,
260
      "Bad Request. User course registration does not exist or admin not allowed to downgrade own role"
261
    )
262

263
    response(403, "Forbidden. User is in different course, or you are not an admin")
264
  end
265

266
  swagger_path :delete_user do
1✔
267
    delete("/courses/{course_id}/admin/users/{course_reg_id}")
268

269
    summary("Deletes a user from a course")
270
    consumes("application/json")
271

272
    parameters do
273
      course_id(:path, :integer, "Course ID", required: true)
274

275
      courseRegId(
276
        :body,
277
        :integer,
278
        "The course registration of the user whose role is to be updated",
279
        required: true
280
      )
281
    end
282

283
    response(200, "OK")
284

285
    response(
286
      400,
287
      "Bad Request. User course registration does not exist or admin not allowed to delete ownself from course or admins cannot be deleted"
288
    )
289

290
    response(403, "Forbidden. User is in different course, or you are not an admin")
291
  end
292

293
  def swagger_definitions do
294
    %{
1✔
295
      AdminUserInfo:
296
        swagger_schema do
1✔
297
          title("User")
298
          description("Basic information about the user in this course")
299

300
          properties do
1✔
301
            userId(:integer, "User's ID")
302
            name(:string, "Full name of the user")
303

304
            role(
305
              :string,
306
              "Role of the user in this course. Can be 'student', 'staff', or 'admin'"
307
            )
308

309
            group(
1✔
310
              :string,
311
              "Group the user belongs to in this course. May be null if the user does not belong to any group"
312
            )
313
          end
314
        end,
315
      UsernameAndRole:
316
        swagger_schema do
1✔
317
          title("Username and role")
318
          description("Username and role of the user to add to this course")
319

320
          properties do
1✔
321
            username(:string, "The user's username")
322
            role(:role, "The user's role. Can be 'student', 'staff', or 'admin'")
1✔
323
          end
324
        end
325
    }
326
  end
327
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc