From 3d192baa28067e0f34167018ab5916bf2d996859 Mon Sep 17 00:00:00 2001 From: RicoPleasure Date: Mon, 29 Sep 2025 23:58:52 +0100 Subject: [PATCH 1/6] feat: shifts endpoints --- config/dev.exs | 2 +- .../university/degrees/courses/shifts.ex | 2 + .../exchanges/shift_exchange_request_json.ex | 7 ++-- .../controllers/shifts/shifts_controller.ex | 42 +++++++++++++++++++ .../controllers/university/course_json.ex | 5 ++- .../{shift_json.ex => shifts_json.ex} | 11 ++++- lib/atlas_web/router.ex | 5 +++ test/atlas/feedbacks_test.exs | 0 .../controllers/feedback_controller_test.exs | 0 9 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 lib/atlas_web/controllers/shifts/shifts_controller.ex rename lib/atlas_web/controllers/university/{shift_json.ex => shifts_json.ex} (72%) create mode 100644 test/atlas/feedbacks_test.exs create mode 100644 test/atlas_web/controllers/feedback_controller_test.exs diff --git a/config/dev.exs b/config/dev.exs index 15cf041..3a31517 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -22,7 +22,7 @@ config :atlas, AtlasWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: 4000], check_origin: false, code_reloader: true, - debug_errors: true, + debug_errors: false, secret_key_base: "+3Sc5XJKuK4YFpwINEFjTPHT57LDXshWNe8gMha9b/KkKjXt0h7cYp64JZkoIaFK", watchers: [] diff --git a/lib/atlas/university/degrees/courses/shifts.ex b/lib/atlas/university/degrees/courses/shifts.ex index c7e2205..d27fd20 100644 --- a/lib/atlas/university/degrees/courses/shifts.ex +++ b/lib/atlas/university/degrees/courses/shifts.ex @@ -19,6 +19,7 @@ defmodule Atlas.University.Degrees.Courses.Shifts do Shift |> apply_filters(opts) |> Repo.all() + |> Repo.preload([:timeslots, :enrollments]) end @doc """ @@ -39,6 +40,7 @@ defmodule Atlas.University.Degrees.Courses.Shifts do Shift |> apply_filters(opts) |> Repo.get!(id) + |> Repo.preload([:timeslots, :enrollments]) end @doc """ diff --git a/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex b/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex index ef0c8f4..60adcac 100644 --- a/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex +++ b/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex @@ -2,7 +2,8 @@ defmodule AtlasWeb.ShiftExchangeRequestJSON do @moduledoc """ A module for rendering shift exchange request data in JSON format. """ - alias AtlasWeb.University.{CourseJSON, ShiftJSON} + alias AtlasWeb.University.CourseJSON + alias AtlasWeb.ShiftsJSON @doc """ Renders a list of shift exchange requests as JSON. @@ -28,8 +29,8 @@ defmodule AtlasWeb.ShiftExchangeRequestJSON do %{ id: shift_exchange_request.id, status: shift_exchange_request.status, - from: ShiftJSON.data(shift_exchange_request.from), - to: ShiftJSON.data(shift_exchange_request.to), + from: ShiftsJSON.data(shift_exchange_request.from), + to: ShiftsJSON.data(shift_exchange_request.to), course: CourseJSON.data(shift_exchange_request.from.course), inserted_at: shift_exchange_request.inserted_at } diff --git a/lib/atlas_web/controllers/shifts/shifts_controller.ex b/lib/atlas_web/controllers/shifts/shifts_controller.ex new file mode 100644 index 0000000..1ce7e66 --- /dev/null +++ b/lib/atlas_web/controllers/shifts/shifts_controller.ex @@ -0,0 +1,42 @@ +defmodule AtlasWeb.ShiftsController do + use AtlasWeb, :controller + + alias Atlas.University.Degrees.Courses.Shifts + alias AtlasWeb.ShiftsJSON + + action_fallback AtlasWeb.FallbackController + + def index(conn, attrs) do + { user, _} = Guardian.Plug.current_resource(conn) + + if user_has_elevated_privileges?(user) do + shifts = Shifts.list_shifts(attrs) + conn + |> render(:index, shifts: shifts) + else + conn + |> put_status(:forbidden) + |> json(%{error: "Unauthorized"}) + end + end + + def update(conn, %{ "id" => id } = attrs) do + { user, _} = Guardian.Plug.current_resource(conn) + shift = Shifts.get_shift!(id) + IO.inspect(shift) + + if user_has_elevated_privileges?(user) do + with {:ok, shift} <- Shifts.update_shift(shift, attrs) do + render(conn, :show, shift: shift) + end + else + conn + |> put_status(:forbidden) + |> json(%{error: "Unauthorized"}) + end + end + + defp user_has_elevated_privileges?(user) do + (user && user.type == :admin) || user.type == :professor + end +end diff --git a/lib/atlas_web/controllers/university/course_json.ex b/lib/atlas_web/controllers/university/course_json.ex index fa7d66e..84ac0d0 100644 --- a/lib/atlas_web/controllers/university/course_json.ex +++ b/lib/atlas_web/controllers/university/course_json.ex @@ -1,6 +1,7 @@ defmodule AtlasWeb.University.CourseJSON do alias Atlas.University.Degrees.Courses.Course - alias AtlasWeb.University.{CourseJSON, ShiftJSON} + alias AtlasWeb.University.CourseJSON + alias AtlasWeb.ShiftsJSON def index(%{courses: courses}) do %{courses: for(course <- courses, do: data(course))} @@ -22,7 +23,7 @@ defmodule AtlasWeb.University.CourseJSON do end, shifts: if Ecto.assoc_loaded?(course.shifts) do - for(shift <- course.shifts, do: ShiftJSON.data(shift)) + for(shift <- course.shifts, do: ShiftsJSON.data(shift)) else [] end diff --git a/lib/atlas_web/controllers/university/shift_json.ex b/lib/atlas_web/controllers/university/shifts_json.ex similarity index 72% rename from lib/atlas_web/controllers/university/shift_json.ex rename to lib/atlas_web/controllers/university/shifts_json.ex index 082e404..a03f6dd 100644 --- a/lib/atlas_web/controllers/university/shift_json.ex +++ b/lib/atlas_web/controllers/university/shifts_json.ex @@ -1,7 +1,16 @@ -defmodule AtlasWeb.University.ShiftJSON do +defmodule AtlasWeb.ShiftsJSON do + alias Atlas.University.Degrees.Courses.Shifts.Shift alias AtlasWeb.University.TimeslotJSON + def index(%{shifts: shifts}) do + %{data: for(shift <- shifts, do: data(shift))} + end + + def show(%{shift: shift}) do + %{data: data(shift)} + end + def data(%Shift{} = shift) do %{ id: shift.id, diff --git a/lib/atlas_web/router.ex b/lib/atlas_web/router.ex index a5309ef..0922e7b 100644 --- a/lib/atlas_web/router.ex +++ b/lib/atlas_web/router.ex @@ -93,6 +93,11 @@ defmodule AtlasWeb.Router do get "/students", University.StudentsController, :index + scope "/shifts" do + get "/", ShiftsController, :index + put "/:id", ShiftsController, :update + end + scope "/jobs" do get "/", JobController, :index get "/:id", JobController, :show diff --git a/test/atlas/feedbacks_test.exs b/test/atlas/feedbacks_test.exs new file mode 100644 index 0000000..e69de29 diff --git a/test/atlas_web/controllers/feedback_controller_test.exs b/test/atlas_web/controllers/feedback_controller_test.exs new file mode 100644 index 0000000..e69de29 From 0f2e56154174b35713cc5e9ca091d5dedcef5a1c Mon Sep 17 00:00:00 2001 From: RicoPleasure Date: Tue, 30 Sep 2025 00:09:19 +0100 Subject: [PATCH 2/6] format & credo --- lib/atlas/university/degrees/courses/shifts.ex | 2 -- .../exchanges/shift_exchange_request_json.ex | 2 +- lib/atlas_web/controllers/shifts/shifts_controller.ex | 9 ++++----- lib/atlas_web/controllers/university/course_json.ex | 2 +- lib/atlas_web/controllers/university/shifts_json.ex | 10 ++++++++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/atlas/university/degrees/courses/shifts.ex b/lib/atlas/university/degrees/courses/shifts.ex index d27fd20..c7e2205 100644 --- a/lib/atlas/university/degrees/courses/shifts.ex +++ b/lib/atlas/university/degrees/courses/shifts.ex @@ -19,7 +19,6 @@ defmodule Atlas.University.Degrees.Courses.Shifts do Shift |> apply_filters(opts) |> Repo.all() - |> Repo.preload([:timeslots, :enrollments]) end @doc """ @@ -40,7 +39,6 @@ defmodule Atlas.University.Degrees.Courses.Shifts do Shift |> apply_filters(opts) |> Repo.get!(id) - |> Repo.preload([:timeslots, :enrollments]) end @doc """ diff --git a/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex b/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex index 60adcac..7acdf4a 100644 --- a/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex +++ b/lib/atlas_web/controllers/exchanges/shift_exchange_request_json.ex @@ -2,8 +2,8 @@ defmodule AtlasWeb.ShiftExchangeRequestJSON do @moduledoc """ A module for rendering shift exchange request data in JSON format. """ - alias AtlasWeb.University.CourseJSON alias AtlasWeb.ShiftsJSON + alias AtlasWeb.University.CourseJSON @doc """ Renders a list of shift exchange requests as JSON. diff --git a/lib/atlas_web/controllers/shifts/shifts_controller.ex b/lib/atlas_web/controllers/shifts/shifts_controller.ex index 1ce7e66..f954f9b 100644 --- a/lib/atlas_web/controllers/shifts/shifts_controller.ex +++ b/lib/atlas_web/controllers/shifts/shifts_controller.ex @@ -2,15 +2,15 @@ defmodule AtlasWeb.ShiftsController do use AtlasWeb, :controller alias Atlas.University.Degrees.Courses.Shifts - alias AtlasWeb.ShiftsJSON action_fallback AtlasWeb.FallbackController def index(conn, attrs) do - { user, _} = Guardian.Plug.current_resource(conn) + {user, _} = Guardian.Plug.current_resource(conn) if user_has_elevated_privileges?(user) do shifts = Shifts.list_shifts(attrs) + conn |> render(:index, shifts: shifts) else @@ -20,10 +20,9 @@ defmodule AtlasWeb.ShiftsController do end end - def update(conn, %{ "id" => id } = attrs) do - { user, _} = Guardian.Plug.current_resource(conn) + def update(conn, %{"id" => id} = attrs) do + {user, _} = Guardian.Plug.current_resource(conn) shift = Shifts.get_shift!(id) - IO.inspect(shift) if user_has_elevated_privileges?(user) do with {:ok, shift} <- Shifts.update_shift(shift, attrs) do diff --git a/lib/atlas_web/controllers/university/course_json.ex b/lib/atlas_web/controllers/university/course_json.ex index 84ac0d0..44db3d5 100644 --- a/lib/atlas_web/controllers/university/course_json.ex +++ b/lib/atlas_web/controllers/university/course_json.ex @@ -1,7 +1,7 @@ defmodule AtlasWeb.University.CourseJSON do alias Atlas.University.Degrees.Courses.Course - alias AtlasWeb.University.CourseJSON alias AtlasWeb.ShiftsJSON + alias AtlasWeb.University.CourseJSON def index(%{courses: courses}) do %{courses: for(course <- courses, do: data(course))} diff --git a/lib/atlas_web/controllers/university/shifts_json.ex b/lib/atlas_web/controllers/university/shifts_json.ex index a03f6dd..829b11e 100644 --- a/lib/atlas_web/controllers/university/shifts_json.ex +++ b/lib/atlas_web/controllers/university/shifts_json.ex @@ -1,5 +1,4 @@ defmodule AtlasWeb.ShiftsJSON do - alias Atlas.University.Degrees.Courses.Shifts.Shift alias AtlasWeb.University.TimeslotJSON @@ -17,10 +16,17 @@ defmodule AtlasWeb.ShiftsJSON do number: shift.number, type: shift.type, professor: shift.professor, - timeslots: for(timeslot <- shift.timeslots, do: TimeslotJSON.data(timeslot)), + timeslots: + if Ecto.assoc_loaded?(shift.timeslots) do + for timeslot <- shift.timeslots, do: TimeslotJSON.data(timeslot) + else + [] + end, enrollment_status: if Ecto.assoc_loaded?(shift.enrollments) && shift.enrollments != [] do hd(shift.enrollments).status + else + nil end } end From 1dc043b920614f4f851db081b6a508dcaf2a2ba2 Mon Sep 17 00:00:00 2001 From: RicoPleasure Date: Tue, 30 Sep 2025 00:12:36 +0100 Subject: [PATCH 3/6] fix: remove blank files --- test/atlas/feedbacks_test.exs | 0 test/atlas_web/controllers/feedback_controller_test.exs | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/atlas/feedbacks_test.exs delete mode 100644 test/atlas_web/controllers/feedback_controller_test.exs diff --git a/test/atlas/feedbacks_test.exs b/test/atlas/feedbacks_test.exs deleted file mode 100644 index e69de29..0000000 diff --git a/test/atlas_web/controllers/feedback_controller_test.exs b/test/atlas_web/controllers/feedback_controller_test.exs deleted file mode 100644 index e69de29..0000000 From 80f7bb88c5365e8f8edd953ae2b69ea0c193c9f7 Mon Sep 17 00:00:00 2001 From: RicoPleasure Date: Fri, 3 Oct 2025 16:26:54 +0100 Subject: [PATCH 4/6] feat: index & put endpoints --- .../university/degrees/courses/shifts.ex | 35 +++++++++++++++++++ .../controllers/shifts/shifts_controller.ex | 20 ++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/atlas/university/degrees/courses/shifts.ex b/lib/atlas/university/degrees/courses/shifts.ex index c7e2205..0d72428 100644 --- a/lib/atlas/university/degrees/courses/shifts.ex +++ b/lib/atlas/university/degrees/courses/shifts.ex @@ -5,6 +5,7 @@ defmodule Atlas.University.Degrees.Courses.Shifts do use Atlas.Context alias Atlas.University.Degrees.Courses.Shifts.Shift + alias Ecto.Multi @doc """ Returns the list of shifts. @@ -21,6 +22,13 @@ defmodule Atlas.University.Degrees.Courses.Shifts do |> Repo.all() end + def list_shifts_with_timeslots(opts \\ []) do + Shift + |> apply_filters(opts) + |> Repo.all() + |> Repo.preload([:timeslots]) + end + @doc """ Gets a single shift. @@ -41,6 +49,13 @@ defmodule Atlas.University.Degrees.Courses.Shifts do |> Repo.get!(id) end + def get_shift_with_timeslots(id, opts \\ []) do + Shift + |> apply_filters(opts) + |> Repo.get(id) + |> Repo.preload([:timeslots]) + end + @doc """ Creates a shift. @@ -208,4 +223,24 @@ defmodule Atlas.University.Degrees.Courses.Shifts do def change_timeslot(%Timeslot{} = timeslot, attrs \\ %{}) do Timeslot.changeset(timeslot, attrs) end + + @doc """ + Updates a shift that contains + """ + + def update_shift_with_timeslots(shift, shift_attrs, timeslot_attrs) do + Multi.new() + |> Multi.update(:shift, change_shift(shift, shift_attrs)) + |> then(fn multi -> + timeslot_attrs + |> Enum.with_index() + |> Enum.reduce(multi, fn {timeslot_attr, index}, acc_multi -> + timeslot = get_timeslot!(timeslot_attr["id"]) + changeset = change_timeslot(timeslot, Map.delete(timeslot_attr, "id")) + + Multi.update(acc_multi, {:timeslot, index}, changeset) + end) + end) + |> Repo.transaction() + end end diff --git a/lib/atlas_web/controllers/shifts/shifts_controller.ex b/lib/atlas_web/controllers/shifts/shifts_controller.ex index f954f9b..249e5fd 100644 --- a/lib/atlas_web/controllers/shifts/shifts_controller.ex +++ b/lib/atlas_web/controllers/shifts/shifts_controller.ex @@ -1,6 +1,7 @@ defmodule AtlasWeb.ShiftsController do use AtlasWeb, :controller + alias Atlas.Repo alias Atlas.University.Degrees.Courses.Shifts action_fallback AtlasWeb.FallbackController @@ -9,7 +10,7 @@ defmodule AtlasWeb.ShiftsController do {user, _} = Guardian.Plug.current_resource(conn) if user_has_elevated_privileges?(user) do - shifts = Shifts.list_shifts(attrs) + shifts = Shifts.list_shifts_with_timeslots(attrs) conn |> render(:index, shifts: shifts) @@ -22,11 +23,22 @@ defmodule AtlasWeb.ShiftsController do def update(conn, %{"id" => id} = attrs) do {user, _} = Guardian.Plug.current_resource(conn) - shift = Shifts.get_shift!(id) if user_has_elevated_privileges?(user) do - with {:ok, shift} <- Shifts.update_shift(shift, attrs) do - render(conn, :show, shift: shift) + shift = Shifts.get_shift_with_timeslots(id) + timeslot_attrs = Map.get(attrs, "timeslots", []) + shift_attrs = Map.delete(attrs, "timeslots") + + case Shifts.update_shift_with_timeslots(shift, shift_attrs, timeslot_attrs) do + {:ok, %{shift: updated_shift} = _results} -> + conn + |> render(:show, shift: updated_shift |> Repo.preload([:timeslots])) + + {:error, _operation, changeset, _changeset} -> + conn + |> put_status(:unprocessable_entity) + |> put_view(json: AtlasWeb.ChangesetJSON) + |> render(:error, changeset: changeset) end else conn From 81142a82ff385fa2147e358f17d2dd2f946d8f72 Mon Sep 17 00:00:00 2001 From: RicoPleasure Date: Fri, 3 Oct 2025 16:30:03 +0100 Subject: [PATCH 5/6] fix: debug errors config --- config/dev.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/dev.exs b/config/dev.exs index 3a31517..15cf041 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -22,7 +22,7 @@ config :atlas, AtlasWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: 4000], check_origin: false, code_reloader: true, - debug_errors: false, + debug_errors: true, secret_key_base: "+3Sc5XJKuK4YFpwINEFjTPHT57LDXshWNe8gMha9b/KkKjXt0h7cYp64JZkoIaFK", watchers: [] From 5f01246ede51e5772b3da5572b637f3119414995 Mon Sep 17 00:00:00 2001 From: RicoPleasure Date: Sat, 11 Oct 2025 11:15:14 +0100 Subject: [PATCH 6/6] feat: add timeslot endpoints --- .../university/degrees/courses/shifts.ex | 36 +++++++++++++++--- .../university/degrees/courses/timeslots.ex | 38 +++++++++++++++++++ .../controllers/timeslots_controller.ex | 14 +++++++ lib/atlas_web/router.ex | 4 ++ 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 lib/atlas/university/degrees/courses/timeslots.ex create mode 100644 lib/atlas_web/controllers/timeslots_controller.ex diff --git a/lib/atlas/university/degrees/courses/shifts.ex b/lib/atlas/university/degrees/courses/shifts.ex index 0d72428..e1e2bd0 100644 --- a/lib/atlas/university/degrees/courses/shifts.ex +++ b/lib/atlas/university/degrees/courses/shifts.ex @@ -231,16 +231,42 @@ defmodule Atlas.University.Degrees.Courses.Shifts do def update_shift_with_timeslots(shift, shift_attrs, timeslot_attrs) do Multi.new() |> Multi.update(:shift, change_shift(shift, shift_attrs)) + |> process_timeslots(shift, timeslot_attrs) + |> Repo.transact() + end + + defp process_timeslots(multi, shift, timeslot_attrs) do + multi |> then(fn multi -> timeslot_attrs |> Enum.with_index() |> Enum.reduce(multi, fn {timeslot_attr, index}, acc_multi -> - timeslot = get_timeslot!(timeslot_attr["id"]) - changeset = change_timeslot(timeslot, Map.delete(timeslot_attr, "id")) - - Multi.update(acc_multi, {:timeslot, index}, changeset) + process_timeslot(acc_multi, timeslot_attr, shift.id, index) end) end) - |> Repo.transaction() + end + + defp process_timeslot(multi, timeslot_attr, shift_id, index) do + if timeslot_attr["id"] && timeslot_exists?(timeslot_attr["id"]) do + timeslot = get_timeslot!(timeslot_attr["id"]) + changeset = change_timeslot(timeslot, Map.delete(timeslot_attr, "id")) + Multi.update(multi, {:timeslot, index}, changeset) + else + clean_attrs = + timeslot_attr + |> Map.delete("id") + |> Map.put("shift_id", shift_id) + |> Map.replace("weekday", timeslot_attr["weekday"] |> String.to_atom()) + + changeset = change_timeslot(%Timeslot{}, clean_attrs) + Multi.insert(multi, {:timeslot, index}, changeset) + end + end + + defp timeslot_exists?(id) do + case Repo.get(Timeslot, id) do + nil -> false + _ -> true + end end end diff --git a/lib/atlas/university/degrees/courses/timeslots.ex b/lib/atlas/university/degrees/courses/timeslots.ex new file mode 100644 index 0000000..44b53b7 --- /dev/null +++ b/lib/atlas/university/degrees/courses/timeslots.ex @@ -0,0 +1,38 @@ +defmodule Atlas.University.Degrees.Courses.Timeslots do + @moduledoc """ + The Timeslots context. + """ + use Atlas.Context + alias Atlas.University.Degrees.Courses.Shifts.Timeslot + + @doc """ + Gets a single timeslot. + + ## Examples + + iex> get_timeslot!(123) + %Timeslot{} + + iex> get_timeslot!(456) + ** (Ecto.NoResultsError) + """ + def get_timeslot!(id, opts \\ []) do + Timeslot + |> apply_filters(opts) + |> Repo.get!(id) + end + + @doc """ + Deletes a timeslot. + ## Examples + + iex> delete_timeslot(timeslot) + {:ok, %Timeslot{}} + + iex> delete_timeslot(timeslot) + {:error, %Ecto.Changeset{}} + """ + def delete_timeslot(%Timeslot{} = timeslot) do + Repo.delete(timeslot) + end +end diff --git a/lib/atlas_web/controllers/timeslots_controller.ex b/lib/atlas_web/controllers/timeslots_controller.ex new file mode 100644 index 0000000..be018c3 --- /dev/null +++ b/lib/atlas_web/controllers/timeslots_controller.ex @@ -0,0 +1,14 @@ +defmodule AtlasWeb.TimeslotsController do + use AtlasWeb, :controller + + alias Atlas.University.Degrees.Courses.Shifts.Timeslot + alias Atlas.University.Degrees.Courses.Timeslots + + def delete(conn, %{"id" => id}) do + timeslot = Timeslots.get_timeslot!(id) + + with {:ok, %Timeslot{}} <- Timeslots.delete_timeslot(timeslot) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/atlas_web/router.ex b/lib/atlas_web/router.ex index 0922e7b..35fc66c 100644 --- a/lib/atlas_web/router.ex +++ b/lib/atlas_web/router.ex @@ -98,6 +98,10 @@ defmodule AtlasWeb.Router do put "/:id", ShiftsController, :update end + scope "/timeslots" do + delete "/:id", TimeslotsController, :delete + end + scope "/jobs" do get "/", JobController, :index get "/:id", JobController, :show