• 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

22.22
/lib/cadet_web/router.ex
1
defmodule CadetWeb.Router do
2
  use CadetWeb, :router
3

4
  pipeline :api do
26✔
5
    plug(:accepts, ["json"])
6
    plug(:fetch_session)
7
    plug(:put_secure_browser_headers)
8
  end
9

10
  pipeline :auth do
26✔
11
    plug(Cadet.Auth.Pipeline)
12
    plug(CadetWeb.Plug.AssignCurrentUser)
13
  end
14

15
  pipeline :ensure_auth do
11✔
16
    plug(Guardian.Plug.EnsureAuthenticated)
17
  end
18

UNCOV
19
  pipeline :rate_limit do
×
20
    plug(CadetWeb.Plugs.RateLimiter)
21
  end
22

UNCOV
23
  pipeline :course do
×
24
    plug(:assign_course)
25
  end
26

UNCOV
27
  pipeline :ensure_staff do
×
28
    plug(:ensure_role, [:staff, :admin])
29
  end
30

UNCOV
31
  pipeline :ensure_admin do
×
32
    plug(:ensure_role, [:admin])
33
  end
34

35
  scope "/", CadetWeb do
36
    get("/.well-known/jwks.json", JWKSController, :index)
4✔
37
  end
38

39
  scope "/sso" do
40
    forward("/", Samly.Router)
×
41
  end
42

43
  # V2 API
44

45
  # Public Pages
46
  scope "/v2", CadetWeb do
47
    pipe_through([:api, :auth])
48

49
    # get("/sourcecast", SourcecastController, :index)
50
    post("/auth/refresh", AuthController, :refresh)
2✔
51
    post("/auth/login", AuthController, :create)
6✔
52
    post("/auth/logout", AuthController, :logout)
2✔
53
    get("/auth/saml_redirect", AuthController, :saml_redirect)
5✔
54
  end
55

56
  scope "/v2", CadetWeb do
57
    # no sessions or anything here
58

59
    get("/devices/:secret/cert", DevicesController, :get_cert)
3✔
60
    get("/devices/:secret/key", DevicesController, :get_key)
3✔
61
    get("/devices/:secret/client_id", DevicesController, :get_client_id)
2✔
62
    get("/devices/:secret/mqtt_endpoint", DevicesController, :get_mqtt_endpoint)
1✔
63
  end
64

65
  # Authenticated Pages without course
66
  scope "/v2", CadetWeb do
67
    pipe_through([:api, :auth, :ensure_auth])
68

69
    get("/user", UserController, :index)
2✔
70
    get("/user/latest_viewed_course", UserController, :get_latest_viewed)
2✔
UNCOV
71
    put("/user/latest_viewed_course", UserController, :update_latest_viewed)
×
72

UNCOV
73
    post("/config/create", CoursesController, :create)
×
74

75
    get("/devices", DevicesController, :index)
1✔
76
    post("/devices", DevicesController, :register)
1✔
77
    post("/devices/:id", DevicesController, :edit)
1✔
78
    delete("/devices/:id", DevicesController, :deregister)
1✔
79
    get("/devices/:id/ws_endpoint", DevicesController, :get_ws_endpoint)
1✔
80
  end
81

82
  # LLM-related endpoints
83
  scope "/v2/chats", CadetWeb do
84
    pipe_through([:api, :auth, :ensure_auth, :rate_limit])
85

86
    post("", ChatController, :init_chat)
1✔
UNCOV
87
    post("/:conversationId/message", ChatController, :chat)
×
88
  end
89

90
  # Authenticated Pages with course
91
  scope "/v2/courses/:course_id", CadetWeb do
92
    pipe_through([:api, :auth, :ensure_auth, :course])
93

UNCOV
94
    get("/sourcecast", SourcecastController, :index)
×
95

UNCOV
96
    get("/assessments", AssessmentsController, :index)
×
UNCOV
97
    get("/assessments/:assessmentid", AssessmentsController, :show)
×
UNCOV
98
    post("/assessments/:assessmentid/unlock", AssessmentsController, :unlock)
×
UNCOV
99
    post("/assessments/:assessmentid/submit", AssessmentsController, :submit)
×
UNCOV
100
    post("/assessments/question/:questionid/answer", AnswerController, :submit)
×
101

UNCOV
102
    post(
×
103
      "/assessments/question/:questionid/answerLastModified",
104
      AnswerController,
105
      :check_last_modified
106
    )
107

UNCOV
108
    get("/achievements", IncentivesController, :index_achievements)
×
UNCOV
109
    get("/self/goals", IncentivesController, :index_goals)
×
UNCOV
110
    post("/self/goals/:uuid/progress", IncentivesController, :update_progress)
×
111

UNCOV
112
    get("/stories", StoriesController, :index)
×
113

UNCOV
114
    get("/notifications", NotificationsController, :index)
×
UNCOV
115
    post("/notifications/acknowledge", NotificationsController, :acknowledge)
×
116

UNCOV
117
    get("/user/total_xp", UserController, :combined_total_xp)
×
UNCOV
118
    put("/user/game_states", UserController, :update_game_states)
×
UNCOV
119
    put("/user/research_agreement", UserController, :update_research_agreement)
×
120

UNCOV
121
    get("/config", CoursesController, :index)
×
NEW
122
    post("/resume_code", CoursesController, :check_resume_code)
×
123

UNCOV
124
    get("/team/:assessmentid", TeamController, :index)
×
125
  end
126

127
  # Admin pages (Access: Course administrators only - these routes can cause substantial damage)
128
  @doc """
129
    NOTE: This scope must come before the routes for all staff below.
130

131
    This is due to the all-staff route "/grading/:submissionid/:questionid", which would pattern match
132
    and overshadow "/grading/:assessmentid/publish_all_grades".
133

134
    If an admin route will overshadow an all-staff route as well, a suggested better solution would be a
135
    per-route permission level check.
136
  """
137
  scope "/v2/courses/:course_id/admin", CadetWeb do
138
    pipe_through([:api, :auth, :ensure_auth, :course, :ensure_admin])
139

UNCOV
140
    get("/assets/:foldername", AdminAssetsController, :index)
×
UNCOV
141
    post("/assets/:foldername/*filename", AdminAssetsController, :upload)
×
UNCOV
142
    delete("/assets/:foldername/*filename", AdminAssetsController, :delete)
×
143

UNCOV
144
    post("/assessments", AdminAssessmentsController, :create)
×
UNCOV
145
    post("/assessments/:assessmentid", AdminAssessmentsController, :update)
×
UNCOV
146
    delete("/assessments/:assessmentid", AdminAssessmentsController, :delete)
×
147

148
    get("/grading/all_submissions", AdminGradingController, :index_all_submissions)
×
149

UNCOV
150
    post(
×
151
      "/grading/:assessmentid/publish_all_grades",
152
      AdminGradingController,
153
      :publish_all_grades
154
    )
155

UNCOV
156
    post(
×
157
      "/grading/:assessmentid/unpublish_all_grades",
158
      AdminGradingController,
159
      :unpublish_all_grades
160
    )
161

UNCOV
162
    put("/users/:course_reg_id/role", AdminUserController, :update_role)
×
UNCOV
163
    delete("/users/:course_reg_id", AdminUserController, :delete_user)
×
164

UNCOV
165
    put("/config", AdminCoursesController, :update_course_config)
×
166
    # TODO: Missing corresponding Swagger path entry
UNCOV
167
    get("/config/assessment_configs", AdminCoursesController, :get_assessment_configs)
×
UNCOV
168
    put("/config/assessment_configs", AdminCoursesController, :update_assessment_configs)
×
169
    # TODO: Missing corresponding Swagger path entry
UNCOV
170
    delete(
×
171
      "/config/assessment_config/:assessment_config_id",
172
      AdminCoursesController,
173
      :delete_assessment_config
174
    )
175
  end
176

177
  # Admin pages (Access: All staff)
178
  scope "/v2/courses/:course_id/admin", CadetWeb do
179
    pipe_through([:api, :auth, :ensure_auth, :course, :ensure_staff])
180

181
    resources("/sourcecast", AdminSourcecastController, only: [:create, :delete])
182

UNCOV
183
    get(
×
184
      "/assessments/:assessmentid/popularVoteLeaderboard",
185
      AdminAssessmentsController,
186
      :get_popular_leaderboard
187
    )
188

UNCOV
189
    get(
×
190
      "/assessments/:assessmentid/scoreLeaderboard",
191
      AdminAssessmentsController,
192
      :get_score_leaderboard
193
    )
194

UNCOV
195
    get("/grading", AdminGradingController, :index)
×
UNCOV
196
    get("/grading/summary", AdminGradingController, :grading_summary)
×
197

UNCOV
198
    get("/grading/:submissionid", AdminGradingController, :show)
×
UNCOV
199
    post("/grading/:submissionid/unsubmit", AdminGradingController, :unsubmit)
×
UNCOV
200
    post("/grading/:submissionid/unpublish_grades", AdminGradingController, :unpublish_grades)
×
UNCOV
201
    post("/grading/:submissionid/publish_grades", AdminGradingController, :publish_grades)
×
UNCOV
202
    post("/grading/:submissionid/autograde", AdminGradingController, :autograde_submission)
×
UNCOV
203
    post("/grading/:submissionid/:questionid", AdminGradingController, :update)
×
204

UNCOV
205
    post(
×
206
      "/grading/:submissionid/:questionid/autograde",
207
      AdminGradingController,
208
      :autograde_answer
209
    )
210

UNCOV
211
    get("/users", AdminUserController, :index)
×
212
    get("/users/teamformation", AdminUserController, :get_students)
×
UNCOV
213
    put("/users", AdminUserController, :upsert_users_and_groups)
×
UNCOV
214
    get("/users/:course_reg_id/assessments", AdminAssessmentsController, :index)
×
215

216
    # The admin route for getting assessment information for a specifc user
217
    # TODO: Missing Swagger path
218
    get(
×
219
      "/users/:course_reg_id/assessments/:assessmentid",
220
      AdminAssessmentsController,
221
      :get_assessment
222
    )
223

224
    # The admin route for getting total xp of a specific user
UNCOV
225
    get("/users/:course_reg_id/total_xp", AdminUserController, :combined_total_xp)
×
UNCOV
226
    get("/users/:course_reg_id/goals", AdminGoalsController, :index_goals_with_progress)
×
UNCOV
227
    post("/users/:course_reg_id/goals/:uuid/progress", AdminGoalsController, :update_progress)
×
228

229
    put("/achievements", AdminAchievementsController, :bulk_update)
1✔
UNCOV
230
    put("/achievements/:uuid", AdminAchievementsController, :update)
×
UNCOV
231
    delete("/achievements/:uuid", AdminAchievementsController, :delete)
×
232

UNCOV
233
    get("/goals", AdminGoalsController, :index)
×
UNCOV
234
    put("/goals", AdminGoalsController, :bulk_update)
×
UNCOV
235
    put("/goals/:uuid", AdminGoalsController, :update)
×
UNCOV
236
    delete("/goals/:uuid", AdminGoalsController, :delete)
×
237

UNCOV
238
    post("/stories", AdminStoriesController, :create)
×
UNCOV
239
    delete("/stories/:storyid", AdminStoriesController, :delete)
×
UNCOV
240
    post("/stories/:storyid", AdminStoriesController, :update)
×
241

UNCOV
242
    get("/teams", AdminTeamsController, :index)
×
UNCOV
243
    post("/teams", AdminTeamsController, :create)
×
UNCOV
244
    delete("/teams/:teamid", AdminTeamsController, :delete)
×
UNCOV
245
    put("/teams/:teamid", AdminTeamsController, :update)
×
246
    post("/teams/upload", AdminTeamsController, :bulk_upload)
×
247
  end
248

249
  # Other scopes may use custom stacks.
250
  # scope "/api", CadetWeb do
251
  #   pipe_through :api
252
  # end
253

254
  def swagger_info do
255
    %{
1✔
256
      info: %{
257
        version: "2.0",
258
        title: "cadet"
259
      },
260
      basePath: "/v2",
261
      securityDefinitions: %{
262
        JWT: %{
263
          type: "apiKey",
264
          in: "header",
265
          name: "Authorization"
266
        }
267
      }
268
    }
269
  end
270

271
  scope "/swagger" do
272
    forward("/", PhoenixSwagger.Plug.SwaggerUI, otp_app: :cadet, swagger_file: "swagger.json")
1✔
273
  end
274

275
  scope "/", CadetWeb do
276
    get("/", DefaultController, :index)
1✔
277
  end
278

279
  if Mix.env() == :dev do
280
    forward("/sent_emails", Bamboo.SentEmailViewerPlug)
281
  end
282

283
  defp assign_course(conn, _opts) do
UNCOV
284
    course_id = conn.path_params["course_id"]
×
285

UNCOV
286
    course_reg =
×
UNCOV
287
      Cadet.Accounts.CourseRegistrations.get_user_record(conn.assigns.current_user.id, course_id)
×
288

UNCOV
289
    case course_reg do
×
UNCOV
290
      nil -> conn |> send_resp(403, "Forbidden") |> halt()
×
UNCOV
291
      cr -> assign(conn, :course_reg, cr)
×
292
    end
293
  end
294

295
  defp ensure_role(conn, opts) do
UNCOV
296
    if not is_nil(conn.assigns.current_user) and conn.assigns.course_reg.role in opts do
×
UNCOV
297
      conn
×
298
    else
299
      conn
300
      |> send_resp(403, "Forbidden")
UNCOV
301
      |> halt()
×
302
    end
303
  end
304
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