Skip to content

Commit 6e4ade6

Browse files
bnjmnt4nqreoct
andauthored
Introduce Cadet API v2 (#721)
* Update `/assets` endpoint (#713) * Assets: remove role validation in model * AssetsController: update API endpoint and validation The endpoint is updated to `/v2/admin/assets`, and validation is performed within the router instead. * Shift to `AdminAssetsController` * Rename `AdminAssetsController` to `.exs` * Assets: switch to `case` instead of `with` * (1) Clearer Swagger documentation in Grading (2) Code documentation cleaup in auth_controller * Notifications changed to plural throughout * Settings swagger docs cleanup * User docs typo fix * CI fix formatting * Update `/settings` endpoint (#722) * `/auth`: shift `/auth` to `/auth/login` * `/devices`: fix typo and shift to v2 * Update `/grading` endpoint (#727) * shift role validation for Settings * shift Grading controller to AdminGrading * fix formatting and add details to docs Co-authored-by: Benjamin Tan <benjamin@dev.ofcr.se> * Rename `assets_controller_test` to `admin_assets_controller_test` * update swagger docs for /grading * Update `/assessments` endpoint (#729) * `/assessments`: split out separate `/admin` endpoint * Consistently use `/assessments/:assessmentid` * Merge `/admin/assessments/:assessmentid/{update,publish}` endpoints * Quick swagger docs fix for `AdminAssessmentsController` * Format code via `mix format` * Formatting fix * `/assessments/{assessmentid}/submit`: shift role validation to controller * Add more guards * `/assessments/question/{questionid}/submit`: shift role validation to AnswerController * clean up some docs * Modify `AnswerController#submit` path * Assessments: remove unused definitions * Remove leftover unused route * Split `AssesmentsController#unlock` out of `AssessmentsController#show` `AssesmentsController#unlock` unlocks password-protected assessments and creates a Submission for them, following which the assessments do not require requests with the password. * Fix formatting issues * `/assessments`: split out separate `/admin` endpoint * Consistently use `/assessments/:assessmentid` * Merge `/admin/assessments/:assessmentid/{update,publish}` endpoints * Quick swagger docs fix for `AdminAssessmentsController` * Format code via `mix format` * `/assessments/{assessmentid}/submit`: shift role validation to controller * Add more guards * `/assessments/question/{questionid}/submit`: shift role validation to AnswerController * clean up some docs * Modify `AnswerController#submit` path * Assessments: remove unused definitions * Remove leftover unused route * Split `AssesmentsController#unlock` out of `AssessmentsController#show` `AssesmentsController#unlock` unlocks password-protected assessments and creates a Submission for them, following which the assessments do not require requests with the password. * Fix formatting issues * formatting * Update per PR feedback * Remove unused variable Co-authored-by: qreoct <9080974+qreoct@users.noreply.github.com> * Change to v2 API throughout * Fix Swagger references * AdminGradingControllerTest: Add swagger path definition * Remove duplicated route * Rename file as per convention * Swagger documentation: Switch to enums for string values * /auth/login: shift parameters to query string in docs * UserController: add an empty UserGameStates map to the schema * Add comments to clarify invalid OpenAPI 2.0 types * Swagger: shift request parameters to separate schemas Request parameters were previously declared as separate body parameters in the Swagger schema, which was technically invalid. For endpoints which require more than 1 parameter, which is sent as JSON, I've created separated schemas which represent the parameters, to make the generated `swagger.json` more compliant. * Story: Fix inaccurate Swagger documentation * Shift all Swagger enums into their own schemas This allows `swagger-typescript-api` in `cadet-frontend` to generate TypeScript `enum`s for the corresponding type. * Fix inaccurate types * Update based on code review feedback * clean up docs * clean up docs * Endpoints: update to mark as required * More changes to the Swagger types * Even more type updates * more swagger docs consistency * /admin/assessments: update swagger to FormData parameters Co-authored-by: qreoct <9080974+qreoct@users.noreply.github.com>
1 parent 66d1ac2 commit 6e4ade6

Some content is hidden

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

42 files changed

+1452
-1288
lines changed

lib/cadet/assessments/assessments.ex

Lines changed: 219 additions & 288 deletions
Large diffs are not rendered by default.

lib/cadet/assessments/library.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
defmodule Cadet.Assessments.Library do
22
@moduledoc """
3-
The library entity represents a library to be used in a question.
3+
The library entity represents a library to be used in a question.
44
"""
55
use Cadet, :model
66

lib/cadet/assets/assets.ex

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,14 @@ defmodule Cadet.Assets.Assets do
55
Assessments context contains domain logic for assets management
66
for Source academy's game component
77
"""
8-
@manage_assets_role ~w(staff admin)a
9-
108
@accessible_folders ~w(images locations objects avatars ui stories sfx bgm) ++
119
if(Mix.env() == :test, do: ["testFolder"], else: [])
1210
@accepted_file_types ~w(.jpg .jpeg .gif .png .wav .mp3 .txt)
1311

14-
def upload_to_s3(upload_params, folder_name, file_name, user) do
12+
def upload_to_s3(upload_params, folder_name, file_name) do
1513
file_type = Path.extname(file_name)
1614

17-
with :ok <- validate_role(user.role),
18-
:ok <- validate_file_name(file_name),
15+
with :ok <- validate_file_name(file_name),
1916
:ok <- validate_folder_name(folder_name),
2017
:ok <- validate_file_type(file_type) do
2118
if object_exists?(folder_name, file_name) do
@@ -35,19 +32,21 @@ defmodule Cadet.Assets.Assets do
3532
end
3633
end
3734

38-
def list_assets(folder_name, user) do
39-
with :ok <- validate_role(user.role),
40-
:ok <- validate_folder_name(folder_name) do
41-
bucket()
42-
|> S3.list_objects(prefix: folder_name <> "/")
43-
|> ExAws.stream!()
44-
|> Enum.map(fn file -> file.key end)
35+
def list_assets(folder_name) do
36+
case validate_folder_name(folder_name) do
37+
:ok ->
38+
bucket()
39+
|> S3.list_objects(prefix: folder_name <> "/")
40+
|> ExAws.stream!()
41+
|> Enum.map(fn file -> file.key end)
42+
43+
{:error, _} = error ->
44+
error
4545
end
4646
end
4747

48-
def delete_object(folder_name, file_name, user) do
49-
with :ok <- validate_role(user.role),
50-
:ok <- validate_file_name(file_name),
48+
def delete_object(folder_name, file_name) do
49+
with :ok <- validate_file_name(file_name),
5150
:ok <- validate_folder_name(folder_name) do
5251
if object_exists?(folder_name, file_name) do
5352
bucket()
@@ -71,14 +70,6 @@ defmodule Cadet.Assets.Assets do
7170
end
7271
end
7372

74-
defp validate_role(role) do
75-
if role in @manage_assets_role do
76-
:ok
77-
else
78-
{:error, {:forbidden, "User not allowed to manage assets"}}
79-
end
80-
end
81-
8273
defp validate_folder_name(folder_name) do
8374
if folder_name in @accessible_folders do
8475
:ok
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
defmodule CadetWeb.AdminAssessmentsController do
2+
use CadetWeb, :controller
3+
4+
use PhoenixSwagger
5+
6+
alias Cadet.Assessments
7+
import Cadet.Updater.XMLParser, only: [parse_xml: 2]
8+
9+
def create(conn, %{"assessment" => assessment, "forceUpdate" => force_update}) do
10+
file =
11+
assessment["file"].path
12+
|> File.read!()
13+
14+
result =
15+
case force_update do
16+
"true" -> parse_xml(file, true)
17+
"false" -> parse_xml(file, false)
18+
end
19+
20+
case result do
21+
:ok ->
22+
if force_update == "true" do
23+
text(conn, "Force update OK")
24+
else
25+
text(conn, "OK")
26+
end
27+
28+
{:ok, warning_message} ->
29+
text(conn, warning_message)
30+
31+
{:error, {status, message}} ->
32+
conn
33+
|> put_status(status)
34+
|> text(message)
35+
end
36+
end
37+
38+
def delete(conn, %{"assessmentid" => assessment_id}) do
39+
result = Assessments.delete_assessment(assessment_id)
40+
41+
case result do
42+
{:ok, _nil} ->
43+
text(conn, "OK")
44+
45+
{:error, {status, message}} ->
46+
conn
47+
|> put_status(status)
48+
|> text(message)
49+
end
50+
end
51+
52+
def update(conn, params = %{"assessmentid" => assessment_id}) when is_ecto_id(assessment_id) do
53+
open_at = params |> Map.get("openAt")
54+
close_at = params |> Map.get("closeAt")
55+
is_published = params |> Map.get("isPublished")
56+
57+
updated_assessment =
58+
if is_nil(is_published) do
59+
%{}
60+
else
61+
%{:is_published => is_published}
62+
end
63+
64+
with {:ok, assessment} <- check_dates(open_at, close_at, updated_assessment),
65+
{:ok, _nil} <- Assessments.update_assessment(assessment_id, assessment) do
66+
text(conn, "OK")
67+
else
68+
{:error, {status, message}} ->
69+
conn
70+
|> put_status(status)
71+
|> text(message)
72+
end
73+
end
74+
75+
defp check_dates(open_at, close_at, assessment) do
76+
if is_nil(open_at) and is_nil(close_at) do
77+
{:ok, assessment}
78+
else
79+
formatted_open_date = elem(DateTime.from_iso8601(open_at), 1)
80+
formatted_close_date = elem(DateTime.from_iso8601(close_at), 1)
81+
82+
if Timex.before?(formatted_close_date, formatted_open_date) do
83+
{:error, {:bad_request, "New end date should occur after new opening date"}}
84+
else
85+
assessment = Map.put(assessment, :open_at, formatted_open_date)
86+
assessment = Map.put(assessment, :close_at, formatted_close_date)
87+
88+
{:ok, assessment}
89+
end
90+
end
91+
end
92+
93+
swagger_path :create do
94+
post("/admin/assessments")
95+
96+
summary("Creates a new assessment or updates an existing assessment")
97+
98+
security([%{JWT: []}])
99+
100+
consumes("multipart/form-data")
101+
102+
parameters do
103+
assessment(:formData, :file, "Assessment to create or update", required: true)
104+
forceUpdate(:formData, :boolean, "Force update", required: true)
105+
end
106+
107+
response(200, "OK")
108+
response(400, "XML parse error")
109+
response(403, "Forbidden")
110+
end
111+
112+
swagger_path :delete do
113+
PhoenixSwagger.Path.delete("/admin/assessments/{assessmentId}")
114+
115+
summary("Deletes an assessment")
116+
117+
security([%{JWT: []}])
118+
119+
parameters do
120+
assessmentId(:path, :integer, "Assessment ID", required: true)
121+
end
122+
123+
response(200, "OK")
124+
response(403, "Forbidden")
125+
end
126+
127+
swagger_path :update do
128+
post("/admin/assessments/{assessmentId}")
129+
130+
summary("Updates an assessment")
131+
132+
security([%{JWT: []}])
133+
134+
consumes("application/json")
135+
136+
parameters do
137+
assessmentId(:path, :integer, "Assessment ID", required: true)
138+
139+
assessment(:body, Schema.ref(:AdminUpdateAssessmentPayload), "Updated assessment details",
140+
required: true
141+
)
142+
end
143+
144+
response(200, "OK")
145+
response(401, "Assessment is already opened")
146+
response(403, "Forbidden")
147+
end
148+
149+
def swagger_definitions do
150+
%{
151+
# Schemas for payloads to modify data
152+
AdminUpdateAssessmentPayload:
153+
swagger_schema do
154+
properties do
155+
closeAt(:string, "Open date", required: false)
156+
openAt(:string, "Close date", required: false)
157+
isPublished(:boolean, "Whether the assessment is published", required: false)
158+
end
159+
end
160+
}
161+
end
162+
end

lib/cadet_web/controllers/assets_controller.ex renamed to lib/cadet_web/admin_controllers/admin_assets_controller.ex

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
defmodule CadetWeb.AssetsController do
1+
defmodule CadetWeb.AdminAssetsController do
22
use CadetWeb, :controller
33

44
use PhoenixSwagger
55
alias Cadet.Assets.Assets
66

77
def index(conn, _params = %{"foldername" => foldername}) do
8-
case Assets.list_assets(foldername, conn.assigns.current_user) do
8+
case Assets.list_assets(foldername) do
99
{:error, {status, message}} -> conn |> put_status(status) |> text(message)
1010
assets -> render(conn, "index.json", assets: assets)
1111
end
@@ -15,7 +15,7 @@ defmodule CadetWeb.AssetsController do
1515
def delete(conn, _params = %{"foldername" => foldername, "filename" => filename}) do
1616
filename = Enum.join(filename, "/")
1717

18-
case Assets.delete_object(foldername, filename, conn.assigns.current_user) do
18+
case Assets.delete_object(foldername, filename) do
1919
{:error, {status, message}} -> conn |> put_status(status) |> text(message)
2020
_ -> conn |> put_status(204) |> text('')
2121
end
@@ -28,7 +28,7 @@ defmodule CadetWeb.AssetsController do
2828
}) do
2929
filename = Enum.join(filename, "/")
3030

31-
case Assets.upload_to_s3(upload_params, foldername, filename, conn.assigns.current_user) do
31+
case Assets.upload_to_s3(upload_params, foldername, filename) do
3232
{:error, {status, message}} -> conn |> put_status(status) |> text(message)
3333
resp -> render(conn, "show.json", resp: resp)
3434
end
@@ -60,12 +60,12 @@ defmodule CadetWeb.AssetsController do
6060
end
6161

6262
swagger_path :index do
63-
get("/assets/{foldername}")
63+
get("/admin/assets/{folderName}")
6464

6565
summary("Get a list of all assets in a folder")
6666

6767
parameters do
68-
foldername(:path, :string, "Folder name", required: true)
68+
folderName(:path, :string, "Folder name", required: true)
6969
end
7070

7171
security([%{JWT: []}])
@@ -78,35 +78,33 @@ defmodule CadetWeb.AssetsController do
7878
end
7979

8080
swagger_path :delete do
81-
PhoenixSwagger.Path.delete("/assets/{foldername}/{filename}")
81+
PhoenixSwagger.Path.delete("/admin/assets/{folderName}/{fileName}")
8282

8383
summary("Delete a file from an asset folder")
8484

8585
parameters do
86-
foldername(:path, :string, "Folder name", required: true)
86+
folderName(:path, :string, "Folder name", required: true)
8787

88-
filename(:path, :string, "File path in folder, which may contain subfolders", required: true)
88+
fileName(:path, :string, "File path in folder, which may contain subfolders", required: true)
8989
end
9090

9191
security([%{JWT: []}])
9292

93-
produces("application/json")
94-
9593
response(204, "OK")
9694
response(400, "Invalid folder name, file name or file type")
9795
response(403, "User is not allowed to manage assets")
9896
response(404, "File not found")
9997
end
10098

10199
swagger_path :upload do
102-
post("/assets/{foldername}/{filename}")
100+
post("/admin/assets/{folderName}/{fileName}")
103101

104102
summary("Upload a file to an asset folder")
105103

106104
parameters do
107-
foldername(:path, :string, "Folder name", required: true)
105+
folderName(:path, :string, "Folder name", required: true)
108106

109-
filename(:path, :string, "File path in folder, which may contain subfolders", required: true)
107+
fileName(:path, :string, "File path in folder, which may contain subfolders", required: true)
110108
end
111109

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

lib/cadet_web/admin_controllers/admin_goals_controller.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ defmodule CadetWeb.AdminGoalsController do
100100
end
101101

102102
swagger_path :delete do
103-
PhoenixSwagger.Path.delete("/admin/goals/{id}")
103+
PhoenixSwagger.Path.delete("/admin/goals/{uuid}")
104104

105105
summary("Deletes a goal")
106106
security([%{JWT: []}])

0 commit comments

Comments
 (0)