From fcc4cf55cfc0411ffcb637fb32530ddfd81f7cfa Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 14 Feb 2023 15:32:15 +0800 Subject: [PATCH 01/80] Create NotificationType Model - Create Notifications module and NotificationType model - Start migration for adding notifications - No functionalities added yet only template code --- lib/cadet/notifications.ex | 104 ++++++++++++++++++ lib/cadet/notifications/notification_type.ex | 20 ++++ .../notification_type_controller.ex | 45 ++++++++ lib/cadet_web/views/notification_type_view.ex | 22 ++++ ...230214065925_create_notification_types.exs | 14 +++ .../notifications/notifications_test.exs | 65 +++++++++++ .../notification_type_controller_test.exs | 96 ++++++++++++++++ 7 files changed, 366 insertions(+) create mode 100644 lib/cadet/notifications.ex create mode 100644 lib/cadet/notifications/notification_type.ex create mode 100644 lib/cadet_web/controllers/notification_type_controller.ex create mode 100644 lib/cadet_web/views/notification_type_view.ex create mode 100644 priv/repo/migrations/20230214065925_create_notification_types.exs create mode 100644 test/cadet/notifications/notifications_test.exs create mode 100644 test/cadet_web/controllers/notification_type_controller_test.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex new file mode 100644 index 000000000..565910fd9 --- /dev/null +++ b/lib/cadet/notifications.ex @@ -0,0 +1,104 @@ +defmodule Cadet.Notifications do + @moduledoc """ + The Notifications context. + """ + + import Ecto.Query, warn: false + alias Cadet.Repo + + alias Cadet.Notifications.NotificationType + + @doc """ + Returns the list of notification_types. + + ## Examples + + iex> list_notification_types() + [%NotificationType{}, ...] + + """ + def list_notification_types do + Repo.all(NotificationType) + end + + @doc """ + Gets a single notification_type. + + Raises `Ecto.NoResultsError` if the Notification type does not exist. + + ## Examples + + iex> get_notification_type!(123) + %NotificationType{} + + iex> get_notification_type!(456) + ** (Ecto.NoResultsError) + + """ + def get_notification_type!(id), do: Repo.get!(NotificationType, id) + + @doc """ + Creates a notification_type. + + ## Examples + + iex> create_notification_type(%{field: value}) + {:ok, %NotificationType{}} + + iex> create_notification_type(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_notification_type(attrs \\ %{}) do + %NotificationType{} + |> NotificationType.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a notification_type. + + ## Examples + + iex> update_notification_type(notification_type, %{field: new_value}) + {:ok, %NotificationType{}} + + iex> update_notification_type(notification_type, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_notification_type(%NotificationType{} = notification_type, attrs) do + notification_type + |> NotificationType.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a notification_type. + + ## Examples + + iex> delete_notification_type(notification_type) + {:ok, %NotificationType{}} + + iex> delete_notification_type(notification_type) + {:error, %Ecto.Changeset{}} + + """ + def delete_notification_type(%NotificationType{} = notification_type) do + Repo.delete(notification_type) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking notification_type changes. + + ## Examples + + iex> change_notification_type(notification_type) + %Ecto.Changeset{data: %NotificationType{}} + + """ + def change_notification_type(%NotificationType{} = notification_type, attrs \\ %{}) do + NotificationType.changeset(notification_type, attrs) + end +end diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex new file mode 100644 index 000000000..232d7af1e --- /dev/null +++ b/lib/cadet/notifications/notification_type.ex @@ -0,0 +1,20 @@ +defmodule Cadet.Notifications.NotificationType do + use Ecto.Schema + import Ecto.Changeset + + schema "notification_types" do + field(:is_autopopulated, :boolean, default: false) + field(:is_enabled, :boolean, default: true) + field(:name, :string) + field(:template_file_name, :string) + + timestamps() + end + + @doc false + def changeset(notification_type, attrs) do + notification_type + |> cast(attrs, [:name, :template_file_name, :is_enabled, :is_autopopulated]) + |> validate_required([:name, :template_file_name, :is_enabled, :is_autopopulated]) + end +end diff --git a/lib/cadet_web/controllers/notification_type_controller.ex b/lib/cadet_web/controllers/notification_type_controller.ex new file mode 100644 index 000000000..d199a23f8 --- /dev/null +++ b/lib/cadet_web/controllers/notification_type_controller.ex @@ -0,0 +1,45 @@ +defmodule CadetWeb.NotificationTypeController do + use CadetWeb, :controller + + alias Cadet.Notifications + alias Cadet.Notifications.NotificationType + + # action_fallback(CadetWeb.FallbackController) + + def index(conn, _params) do + notification_types = Notifications.list_notification_types() + render(conn, "index.json", notification_types: notification_types) + end + + def create(conn, %{"notification_type" => notification_type_params}) do + with {:ok, %NotificationType{} = notification_type} <- + Notifications.create_notification_type(notification_type_params) do + conn + |> put_status(:created) + # |> put_resp_header("location", Routes.notification_type_path(conn, :show, notification_type)) + |> render("show.json", notification_type: notification_type) + end + end + + def show(conn, %{"id" => id}) do + notification_type = Notifications.get_notification_type!(id) + render(conn, "show.json", notification_type: notification_type) + end + + def update(conn, %{"id" => id, "notification_type" => notification_type_params}) do + notification_type = Notifications.get_notification_type!(id) + + with {:ok, %NotificationType{} = notification_type} <- + Notifications.update_notification_type(notification_type, notification_type_params) do + render(conn, "show.json", notification_type: notification_type) + end + end + + def delete(conn, %{"id" => id}) do + notification_type = Notifications.get_notification_type!(id) + + with {:ok, %NotificationType{}} <- Notifications.delete_notification_type(notification_type) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/cadet_web/views/notification_type_view.ex b/lib/cadet_web/views/notification_type_view.ex new file mode 100644 index 000000000..036b7085c --- /dev/null +++ b/lib/cadet_web/views/notification_type_view.ex @@ -0,0 +1,22 @@ +defmodule CadetWeb.NotificationTypeView do + use CadetWeb, :view + alias CadetWeb.NotificationTypeView + + def render("index.json", %{notification_types: notification_types}) do + %{data: render_many(notification_types, NotificationTypeView, "notification_type.json")} + end + + def render("show.json", %{notification_type: notification_type}) do + %{data: render_one(notification_type, NotificationTypeView, "notification_type.json")} + end + + def render("notification_type.json", %{notification_type: notification_type}) do + %{ + id: notification_type.id, + name: notification_type.name, + template_file_name: notification_type.template_file_name, + is_enabled: notification_type.is_enabled, + is_autopopulated: notification_type.is_autopopulated + } + end +end diff --git a/priv/repo/migrations/20230214065925_create_notification_types.exs b/priv/repo/migrations/20230214065925_create_notification_types.exs new file mode 100644 index 000000000..f2a10a761 --- /dev/null +++ b/priv/repo/migrations/20230214065925_create_notification_types.exs @@ -0,0 +1,14 @@ +defmodule Cadet.Repo.Migrations.CreateNotificationTypes do + use Ecto.Migration + + def change do + create table(:notification_types) do + add :name, :string + add :template_file_name, :string + add :is_enabled, :boolean, default: false, null: false + add :is_autopopulated, :boolean, default: false, null: false + + timestamps() + end + end +end diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs new file mode 100644 index 000000000..124285927 --- /dev/null +++ b/test/cadet/notifications/notifications_test.exs @@ -0,0 +1,65 @@ +defmodule Cadet.NotificationsTest do + use Cadet.DataCase + + alias Cadet.Notifications + + describe "notification_types" do + alias Cadet.Notifications.NotificationType + + # import Cadet.NotificationsFixtures + + # @invalid_attrs %{is_autopopulated: nil, is_enabled: nil, name: nil, template_file_name: nil} + + # test "list_notification_types/0 returns all notification_types" do + # notification_type = notification_type_fixture() + # assert Notifications.list_notification_types() == [notification_type] + # end + + # test "get_notification_type!/1 returns the notification_type with given id" do + # notification_type = notification_type_fixture() + # assert Notifications.get_notification_type!(notification_type.id) == notification_type + # end + + # test "create_notification_type/1 with valid data creates a notification_type" do + # valid_attrs = %{is_autopopulated: true, is_enabled: true, name: "some name", template_file_name: "some template_file_name"} + + # assert {:ok, %NotificationType{} = notification_type} = Notifications.create_notification_type(valid_attrs) + # assert notification_type.is_autopopulated == true + # assert notification_type.is_enabled == true + # assert notification_type.name == "some name" + # assert notification_type.template_file_name == "some template_file_name" + # end + + # test "create_notification_type/1 with invalid data returns error changeset" do + # assert {:error, %Ecto.Changeset{}} = Notifications.create_notification_type(@invalid_attrs) + # end + + # test "update_notification_type/2 with valid data updates the notification_type" do + # notification_type = notification_type_fixture() + # update_attrs = %{is_autopopulated: false, is_enabled: false, name: "some updated name", template_file_name: "some updated template_file_name"} + + # assert {:ok, %NotificationType{} = notification_type} = Notifications.update_notification_type(notification_type, update_attrs) + # assert notification_type.is_autopopulated == false + # assert notification_type.is_enabled == false + # assert notification_type.name == "some updated name" + # assert notification_type.template_file_name == "some updated template_file_name" + # end + + # test "update_notification_type/2 with invalid data returns error changeset" do + # notification_type = notification_type_fixture() + # assert {:error, %Ecto.Changeset{}} = Notifications.update_notification_type(notification_type, @invalid_attrs) + # assert notification_type == Notifications.get_notification_type!(notification_type.id) + # end + + # test "delete_notification_type/1 deletes the notification_type" do + # notification_type = notification_type_fixture() + # assert {:ok, %NotificationType{}} = Notifications.delete_notification_type(notification_type) + # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_type!(notification_type.id) end + # end + + # test "change_notification_type/1 returns a notification_type changeset" do + # notification_type = notification_type_fixture() + # assert %Ecto.Changeset{} = Notifications.change_notification_type(notification_type) + # end + end +end diff --git a/test/cadet_web/controllers/notification_type_controller_test.exs b/test/cadet_web/controllers/notification_type_controller_test.exs new file mode 100644 index 000000000..a6f79f037 --- /dev/null +++ b/test/cadet_web/controllers/notification_type_controller_test.exs @@ -0,0 +1,96 @@ +defmodule CadetWeb.NotificationTypeControllerTest do + use CadetWeb.ConnCase + + # import Cadet.NotificationsFixtures + + alias Cadet.Notifications.NotificationType + + @create_attrs %{ + is_autopopulated: true, + is_enabled: true, + name: "some name", + template_file_name: "some template_file_name" + } + @update_attrs %{ + is_autopopulated: false, + is_enabled: false, + name: "some updated name", + template_file_name: "some updated template_file_name" + } + @invalid_attrs %{is_autopopulated: nil, is_enabled: nil, name: nil, template_file_name: nil} + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + # describe "index" do + # test "lists all notification_types", %{conn: conn} do + # conn = get(conn, Routes.notification_type_path(conn, :index)) + # assert json_response(conn, 200)["data"] == [] + # end + # end + + # describe "create notification_type" do + # test "renders notification_type when data is valid", %{conn: conn} do + # conn = post(conn, Routes.notification_type_path(conn, :create), notification_type: @create_attrs) + # assert %{"id" => id} = json_response(conn, 201)["data"] + + # conn = get(conn, Routes.notification_type_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_autopopulated" => true, + # "is_enabled" => true, + # "name" => "some name", + # "template_file_name" => "some template_file_name" + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn} do + # conn = post(conn, Routes.notification_type_path(conn, :create), notification_type: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "update notification_type" do + # setup [:create_notification_type] + + # test "renders notification_type when data is valid", %{conn: conn, notification_type: %NotificationType{id: id} = notification_type} do + # conn = put(conn, Routes.notification_type_path(conn, :update, notification_type), notification_type: @update_attrs) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.notification_type_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_autopopulated" => false, + # "is_enabled" => false, + # "name" => "some updated name", + # "template_file_name" => "some updated template_file_name" + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn, notification_type: notification_type} do + # conn = put(conn, Routes.notification_type_path(conn, :update, notification_type), notification_type: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "delete notification_type" do + # setup [:create_notification_type] + + # test "deletes chosen notification_type", %{conn: conn, notification_type: notification_type} do + # conn = delete(conn, Routes.notification_type_path(conn, :delete, notification_type)) + # assert response(conn, 204) + + # assert_error_sent 404, fn -> + # get(conn, Routes.notification_type_path(conn, :show, notification_type)) + # end + # end + # end + + # defp create_notification_type(_) do + # notification_type = notification_type_fixture() + # %{notification_type: notification_type} + # end +end From b429644aa6816dda01dd6552cabf2f530d2e8cf1 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Tue, 14 Feb 2023 16:42:32 +0800 Subject: [PATCH 02/80] Configure Oban and Bamboo for notifications system --- config/config.exs | 13 +++++++++++++ config/prod.exs | 3 +++ mix.exs | 6 ++++++ mix.lock | 5 +++++ .../20230214081421_add_oban_jobs_table.exs | 13 +++++++++++++ 5 files changed, 40 insertions(+) create mode 100644 priv/repo/migrations/20230214081421_add_oban_jobs_table.exs diff --git a/config/config.exs b/config/config.exs index d09152802..ebc792408 100644 --- a/config/config.exs +++ b/config/config.exs @@ -93,3 +93,16 @@ config :guardian, Guardian.DB, token_types: ["refresh"], # default: 60 minute sweep_interval: 180 + +config :cadet, Oban, + repo: Cadet.Repo, + plugins: [ + Oban.Plugins.Pruner, + {Oban.Plugins.Cron, + crontab: [] + } + ], + queues: [default: 10, mail: 50] + +config :cadet, Cadet.Mailer, + adapter: Bamboo.LocalAdapter diff --git a/config/prod.exs b/config/prod.exs index 5c2491d71..6f3fc4b46 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -44,3 +44,6 @@ config :logger, level: :info config :ex_aws, access_key_id: [:instance_role], secret_access_key: [:instance_role] + +config :cadet, Cadet.Mailer, + adapter: Bamboo.SesAdapter diff --git a/mix.exs b/mix.exs index a19de61b7..c389dd800 100644 --- a/mix.exs +++ b/mix.exs @@ -81,6 +81,12 @@ defmodule Cadet.Mixfile do {:sweet_xml, "~> 0.6"}, {:timex, "~> 3.7"}, + # notifiations system dependencies + {:bamboo, "~> 2.3.0"}, + {:bamboo_ses, "~> 0.3.0"}, + {:bamboo_phoenix, "~> 1.0.0"}, + {:oban, "~> 2.13"}, + # development dependencies {:configparser_ex, "~> 4.0", only: [:dev, :test]}, {:credo, "~> 1.0", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index fd2d2d35d..b96fb0111 100644 --- a/mix.lock +++ b/mix.lock @@ -2,6 +2,9 @@ "arc": {:hex, :arc, "0.11.0", "ac7a0cc03035317b6fef9fe94c97d7d9bd183a3e7ce1606aa0c175cfa8d1ba6d", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "e91a8bd676fca716f6e46275ae81fb96c0bbc7a9d5b96cac511ae190588eddd0"}, "arc_ecto": {:hex, :arc_ecto, "0.11.3", "52f278330fe3a29472ce5d9682514ca09eaed4b33453cbaedb5241a491464f7d", [:mix], [{:arc, "~> 0.11.0", [hex: :arc, repo: "hexpm", optional: false]}, {:ecto, ">= 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "24beed35003707434a778caece7d71e46e911d46d1e82e7787345264fc8e96d0"}, "artificery": {:hex, :artificery, "0.4.3", "0bc4260f988dcb9dda4b23f9fc3c6c8b99a6220a331534fdf5bf2fd0d4333b02", [:mix], [], "hexpm", "12e95333a30e20884e937abdbefa3e7f5e05609c2ba8cf37b33f000b9ffc0504"}, + "bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"}, + "bamboo_phoenix": {:hex, :bamboo_phoenix, "1.0.0", "f3cc591ffb163ed0bf935d256f1f4645cd870cf436545601215745fb9cc9953f", [:mix], [{:bamboo, ">= 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.3.0", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "6db88fbb26019c84a47994bb2bd879c0887c29ce6c559bc6385fd54eb8b37dee"}, + "bamboo_ses": {:hex, :bamboo_ses, "0.3.1", "3c172fc5bf2bbb1f9eec632750496ae1e6468cec4c2f0ac2a6b04351a674e2f2", [:mix], [{:bamboo, "~> 2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:ex_aws, "~> 2.4.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "0.2.2", [hex: :mail, repo: "hexpm", optional: false]}], "hexpm", "7e67997479115501da674627b15322a570b41042fc0031be8a5c80e734354c26"}, "blankable": {:hex, :blankable, "1.0.0", "89ab564a63c55af117e115144e3b3b57eb53ad43ba0f15553357eb283e0ed425", [:mix], [], "hexpm", "7cf11aac0e44f4eedbee0c15c1d37d94c090cb72a8d9fddf9f7aec30f9278899"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, @@ -50,12 +53,14 @@ "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, "jose": {:hex, :jose, "1.11.2", "f4c018ccf4fdce22c71e44d471f15f723cb3efab5d909ab2ba202b5bf35557b3", [:mix, :rebar3], [], "hexpm", "98143fbc48d55f3a18daba82d34fe48959d44538e9697c08f34200fa5f0947d2"}, "jsx": {:hex, :jsx, "3.1.0", "d12516baa0bb23a59bb35dccaf02a1bd08243fcbb9efe24f2d9d056ccff71268", [:rebar3], [], "hexpm", "0c5cc8fdc11b53cc25cf65ac6705ad39e54ecc56d1c22e4adb8f5a53fb9427f3"}, + "mail": {:hex, :mail, "0.2.2", "b1d31beaa2a7b23d7b84b2794f037ef4dfdaba9e66d877142bedbaf0625b9c16", [:mix], [], "hexpm", "1c9d31548a60c44ded1806369e07a7dd4d05737eb47fa3238bbf2436b3da8a32"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"}, + "oban": {:hex, :oban, "2.14.1", "99e28a814ca9faa759cd3f88d9adc56eb5dd0b8d4a5dabb8d2e989cb57c86f52", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6c368b5face9b1e96ba42a1d39710c5193f4b38b62c8aeb651e37897aa3feecd"}, "openid_connect": {:hex, :openid_connect, "0.2.2", "c05055363330deab39ffd89e609db6b37752f255a93802006d83b45596189c0b", [:mix], [{:httpoison, "~> 1.2", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "735769b6d592124b58edd0582554ce638524c0214cd783d8903d33357d74cc13"}, "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, diff --git a/priv/repo/migrations/20230214081421_add_oban_jobs_table.exs b/priv/repo/migrations/20230214081421_add_oban_jobs_table.exs new file mode 100644 index 000000000..15df03e56 --- /dev/null +++ b/priv/repo/migrations/20230214081421_add_oban_jobs_table.exs @@ -0,0 +1,13 @@ +defmodule Cadet.Repo.Migrations.AddObanJobsTable do + use Ecto.Migration + + def up do + Oban.Migration.up(version: 11) + end + + # We specify `version: 1` in `down`, ensuring that we'll roll all the way back down if + # necessary, regardless of which version we've migrated `up` to. + def down do + Oban.Migration.down(version: 1) + end +end From d024a75d565a99907fd46dcfc1380fc9a4e226ba Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 14 Feb 2023 17:21:22 +0800 Subject: [PATCH 03/80] Create NotificationConfig Model --- lib/cadet/notifications.ex | 95 +++++++++++++++++++ .../notifications/notification_config.ex | 24 +++++ .../notification_config_controller.ex | 49 ++++++++++ .../views/notification_config_view.ex | 19 ++++ ...0214074219_create_notification_configs.exs | 14 +++ .../notifications/notifications_test.exs | 54 +++++++++++ .../notification_config_controller_test.exs | 84 ++++++++++++++++ .../notification_type_controller_test.exs | 38 ++++---- 8 files changed, 358 insertions(+), 19 deletions(-) create mode 100644 lib/cadet/notifications/notification_config.ex create mode 100644 lib/cadet_web/controllers/notification_config_controller.ex create mode 100644 lib/cadet_web/views/notification_config_view.ex create mode 100644 priv/repo/migrations/20230214074219_create_notification_configs.exs create mode 100644 test/cadet_web/controllers/notification_config_controller_test.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 565910fd9..5ea910b2c 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -7,6 +7,7 @@ defmodule Cadet.Notifications do alias Cadet.Repo alias Cadet.Notifications.NotificationType + alias Cadet.Notifications.NotificationConfig @doc """ Returns the list of notification_types. @@ -101,4 +102,98 @@ defmodule Cadet.Notifications do def change_notification_type(%NotificationType{} = notification_type, attrs \\ %{}) do NotificationType.changeset(notification_type, attrs) end + + @doc """ + Returns the list of notification_configs. + + ## Examples + + iex> list_notification_configs() + [%NotificationConfig{}, ...] + + """ + def list_notification_configs do + Repo.all(NotificationConfig) + end + + @doc """ + Gets a single notification_config. + + Raises `Ecto.NoResultsError` if the Notification config does not exist. + + ## Examples + + iex> get_notification_config!(123) + %NotificationConfig{} + + iex> get_notification_config!(456) + ** (Ecto.NoResultsError) + + """ + def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) + + @doc """ + Creates a notification_config. + + ## Examples + + iex> create_notification_config(%{field: value}) + {:ok, %NotificationConfig{}} + + iex> create_notification_config(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_notification_config(attrs \\ %{}) do + %NotificationConfig{} + |> NotificationConfig.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a notification_config. + + ## Examples + + iex> update_notification_config(notification_config, %{field: new_value}) + {:ok, %NotificationConfig{}} + + iex> update_notification_config(notification_config, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_notification_config(%NotificationConfig{} = notification_config, attrs) do + notification_config + |> NotificationConfig.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a notification_config. + + ## Examples + + iex> delete_notification_config(notification_config) + {:ok, %NotificationConfig{}} + + iex> delete_notification_config(notification_config) + {:error, %Ecto.Changeset{}} + + """ + def delete_notification_config(%NotificationConfig{} = notification_config) do + Repo.delete(notification_config) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking notification_config changes. + + ## Examples + + iex> change_notification_config(notification_config) + %Ecto.Changeset{data: %NotificationConfig{}} + + """ + def change_notification_config(%NotificationConfig{} = notification_config, attrs \\ %{}) do + NotificationConfig.changeset(notification_config, attrs) + end end diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex new file mode 100644 index 000000000..82f38c8a6 --- /dev/null +++ b/lib/cadet/notifications/notification_config.ex @@ -0,0 +1,24 @@ +defmodule Cadet.Notifications.NotificationConfig do + use Ecto.Schema + import Ecto.Changeset + alias Cadet.Courses.Course + alias Cadet.Courses.AssessmentConfig + alias Cadet.Notifications.NotificationType + + schema "notification_configs" do + field(:is_enabled, :boolean, default: false) + + belongs_to(:notification_type, NotificationType) + belongs_to(:course, Course) + belongs_to(:assessment_config, AssessmentConfig) + + timestamps() + end + + @doc false + def changeset(notification_config, attrs) do + notification_config + |> cast(attrs, [:is_enabled]) + |> validate_required([:is_enabled]) + end +end diff --git a/lib/cadet_web/controllers/notification_config_controller.ex b/lib/cadet_web/controllers/notification_config_controller.ex new file mode 100644 index 000000000..e545fc3c7 --- /dev/null +++ b/lib/cadet_web/controllers/notification_config_controller.ex @@ -0,0 +1,49 @@ +defmodule CadetWeb.NotificationConfigController do + use CadetWeb, :controller + + alias Cadet.Notifications + alias Cadet.Notifications.NotificationConfig + + action_fallback(CadetWeb.FallbackController) + + def index(conn, _params) do + notification_configs = Notifications.list_notification_configs() + render(conn, "index.json", notification_configs: notification_configs) + end + + def create(conn, %{"notification_config" => notification_config_params}) do + with {:ok, %NotificationConfig{} = notification_config} <- + Notifications.create_notification_config(notification_config_params) do + conn + |> put_status(:created) + # |> put_resp_header("location", Routes.notification_config_path(conn, :show, notification_config)) + |> render("show.json", notification_config: notification_config) + end + end + + def show(conn, %{"id" => id}) do + notification_config = Notifications.get_notification_config!(id) + render(conn, "show.json", notification_config: notification_config) + end + + def update(conn, %{"id" => id, "notification_config" => notification_config_params}) do + notification_config = Notifications.get_notification_config!(id) + + with {:ok, %NotificationConfig{} = notification_config} <- + Notifications.update_notification_config( + notification_config, + notification_config_params + ) do + render(conn, "show.json", notification_config: notification_config) + end + end + + def delete(conn, %{"id" => id}) do + notification_config = Notifications.get_notification_config!(id) + + with {:ok, %NotificationConfig{}} <- + Notifications.delete_notification_config(notification_config) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/cadet_web/views/notification_config_view.ex b/lib/cadet_web/views/notification_config_view.ex new file mode 100644 index 000000000..f231cd1ab --- /dev/null +++ b/lib/cadet_web/views/notification_config_view.ex @@ -0,0 +1,19 @@ +defmodule CadetWeb.NotificationConfigView do + use CadetWeb, :view + alias CadetWeb.NotificationConfigView + + def render("index.json", %{notification_configs: notification_configs}) do + %{data: render_many(notification_configs, NotificationConfigView, "notification_config.json")} + end + + def render("show.json", %{notification_config: notification_config}) do + %{data: render_one(notification_config, NotificationConfigView, "notification_config.json")} + end + + def render("notification_config.json", %{notification_config: notification_config}) do + %{ + id: notification_config.id, + is_enabled: notification_config.is_enabled + } + end +end diff --git a/priv/repo/migrations/20230214074219_create_notification_configs.exs b/priv/repo/migrations/20230214074219_create_notification_configs.exs new file mode 100644 index 000000000..340bf1a55 --- /dev/null +++ b/priv/repo/migrations/20230214074219_create_notification_configs.exs @@ -0,0 +1,14 @@ +defmodule Cadet.Repo.Migrations.CreateNotificationConfigs do + use Ecto.Migration + + def change do + create table(:notification_configs) do + add(:is_enabled, :boolean, default: false, null: false) + add(:notification_type_id, references(:notification_types, on_delete: :delete_all)) + add(:course_id, references(:courses, on_delete: :delete_all)) + add(:assessment_config_id, references(:assessment_configs, on_delete: :delete_all)) + + timestamps() + end + end +end diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 124285927..35b138ba3 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -62,4 +62,58 @@ defmodule Cadet.NotificationsTest do # assert %Ecto.Changeset{} = Notifications.change_notification_type(notification_type) # end end + + describe "notification_configs" do + # alias Cadet.Notifications.NotificationConfig + + # import Cadet.NotificationsFixtures + + # @invalid_attrs %{is_enabled: nil} + + # test "list_notification_configs/0 returns all notification_configs" do + # notification_config = notification_config_fixture() + # assert Notifications.list_notification_configs() == [notification_config] + # end + + # test "get_notification_config!/1 returns the notification_config with given id" do + # notification_config = notification_config_fixture() + # assert Notifications.get_notification_config!(notification_config.id) == notification_config + # end + + # test "create_notification_config/1 with valid data creates a notification_config" do + # valid_attrs = %{is_enabled: true} + + # assert {:ok, %NotificationConfig{} = notification_config} = Notifications.create_notification_config(valid_attrs) + # assert notification_config.is_enabled == true + # end + + # test "create_notification_config/1 with invalid data returns error changeset" do + # assert {:error, %Ecto.Changeset{}} = Notifications.create_notification_config(@invalid_attrs) + # end + + # test "update_notification_config/2 with valid data updates the notification_config" do + # notification_config = notification_config_fixture() + # update_attrs = %{is_enabled: false} + + # assert {:ok, %NotificationConfig{} = notification_config} = Notifications.update_notification_config(notification_config, update_attrs) + # assert notification_config.is_enabled == false + # end + + # test "update_notification_config/2 with invalid data returns error changeset" do + # notification_config = notification_config_fixture() + # assert {:error, %Ecto.Changeset{}} = Notifications.update_notification_config(notification_config, @invalid_attrs) + # assert notification_config == Notifications.get_notification_config!(notification_config.id) + # end + + # test "delete_notification_config/1 deletes the notification_config" do + # notification_config = notification_config_fixture() + # assert {:ok, %NotificationConfig{}} = Notifications.delete_notification_config(notification_config) + # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_config!(notification_config.id) end + # end + + # test "change_notification_config/1 returns a notification_config changeset" do + # notification_config = notification_config_fixture() + # assert %Ecto.Changeset{} = Notifications.change_notification_config(notification_config) + # end + end end diff --git a/test/cadet_web/controllers/notification_config_controller_test.exs b/test/cadet_web/controllers/notification_config_controller_test.exs new file mode 100644 index 000000000..f10eee15d --- /dev/null +++ b/test/cadet_web/controllers/notification_config_controller_test.exs @@ -0,0 +1,84 @@ +defmodule CadetWeb.NotificationConfigControllerTest do + use CadetWeb.ConnCase + + # import Cadet.NotificationsFixtures + + # alias Cadet.Notifications.NotificationConfig + + # @create_attrs %{ + # is_enabled: true + # } + # @update_attrs %{ + # is_enabled: false + # } + # @invalid_attrs %{is_enabled: nil} + + # setup %{conn: conn} do + # {:ok, conn: put_req_header(conn, "accept", "application/json")} + # end + + # describe "index" do + # test "lists all notification_configs", %{conn: conn} do + # conn = get(conn, Routes.notification_config_path(conn, :index)) + # assert json_response(conn, 200)["data"] == [] + # end + # end + + # describe "create notification_config" do + # test "renders notification_config when data is valid", %{conn: conn} do + # conn = post(conn, Routes.notification_config_path(conn, :create), notification_config: @create_attrs) + # assert %{"id" => id} = json_response(conn, 201)["data"] + + # conn = get(conn, Routes.notification_config_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_enabled" => true + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn} do + # conn = post(conn, Routes.notification_config_path(conn, :create), notification_config: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "update notification_config" do + # setup [:create_notification_config] + + # test "renders notification_config when data is valid", %{conn: conn, notification_config: %NotificationConfig{id: id} = notification_config} do + # conn = put(conn, Routes.notification_config_path(conn, :update, notification_config), notification_config: @update_attrs) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.notification_config_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_enabled" => false + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn, notification_config: notification_config} do + # conn = put(conn, Routes.notification_config_path(conn, :update, notification_config), notification_config: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "delete notification_config" do + # setup [:create_notification_config] + + # test "deletes chosen notification_config", %{conn: conn, notification_config: notification_config} do + # conn = delete(conn, Routes.notification_config_path(conn, :delete, notification_config)) + # assert response(conn, 204) + + # assert_error_sent 404, fn -> + # get(conn, Routes.notification_config_path(conn, :show, notification_config)) + # end + # end + # end + + # defp create_notification_config(_) do + # notification_config = notification_config_fixture() + # %{notification_config: notification_config} + # end +end diff --git a/test/cadet_web/controllers/notification_type_controller_test.exs b/test/cadet_web/controllers/notification_type_controller_test.exs index a6f79f037..b7a47f95a 100644 --- a/test/cadet_web/controllers/notification_type_controller_test.exs +++ b/test/cadet_web/controllers/notification_type_controller_test.exs @@ -3,25 +3,25 @@ defmodule CadetWeb.NotificationTypeControllerTest do # import Cadet.NotificationsFixtures - alias Cadet.Notifications.NotificationType - - @create_attrs %{ - is_autopopulated: true, - is_enabled: true, - name: "some name", - template_file_name: "some template_file_name" - } - @update_attrs %{ - is_autopopulated: false, - is_enabled: false, - name: "some updated name", - template_file_name: "some updated template_file_name" - } - @invalid_attrs %{is_autopopulated: nil, is_enabled: nil, name: nil, template_file_name: nil} - - setup %{conn: conn} do - {:ok, conn: put_req_header(conn, "accept", "application/json")} - end + # alias Cadet.Notifications.NotificationType + + # @create_attrs %{ + # is_autopopulated: true, + # is_enabled: true, + # name: "some name", + # template_file_name: "some template_file_name" + # } + # @update_attrs %{ + # is_autopopulated: false, + # is_enabled: false, + # name: "some updated name", + # template_file_name: "some updated template_file_name" + # } + # @invalid_attrs %{is_autopopulated: nil, is_enabled: nil, name: nil, template_file_name: nil} + + # setup %{conn: conn} do + # {:ok, conn: put_req_header(conn, "accept", "application/json")} + # end # describe "index" do # test "lists all notification_types", %{conn: conn} do From cd20b4a35808946ec763d22f3e7608a0af7fde16 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 14 Feb 2023 22:21:12 +0800 Subject: [PATCH 04/80] Create TimeOption Model --- lib/cadet/notifications.ex | 96 +++++++++++++++++++ lib/cadet/notifications/time_option.ex | 21 ++++ .../controllers/time_option_controller.ex | 45 +++++++++ lib/cadet_web/views/time_option_view.ex | 20 ++++ .../20230214132717_create_time_options.exs | 13 +++ .../notifications/notifications_test.exs | 58 ++++++++++- .../time_option_controller_test.exs | 88 +++++++++++++++++ 7 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 lib/cadet/notifications/time_option.ex create mode 100644 lib/cadet_web/controllers/time_option_controller.ex create mode 100644 lib/cadet_web/views/time_option_view.ex create mode 100644 priv/repo/migrations/20230214132717_create_time_options.exs create mode 100644 test/cadet_web/controllers/time_option_controller_test.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 5ea910b2c..af1b6ce15 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -196,4 +196,100 @@ defmodule Cadet.Notifications do def change_notification_config(%NotificationConfig{} = notification_config, attrs \\ %{}) do NotificationConfig.changeset(notification_config, attrs) end + + alias Cadet.Notifications.TimeOption + + @doc """ + Returns the list of time_options. + + ## Examples + + iex> list_time_options() + [%TimeOption{}, ...] + + """ + def list_time_options do + Repo.all(TimeOption) + end + + @doc """ + Gets a single time_option. + + Raises `Ecto.NoResultsError` if the Time option does not exist. + + ## Examples + + iex> get_time_option!(123) + %TimeOption{} + + iex> get_time_option!(456) + ** (Ecto.NoResultsError) + + """ + def get_time_option!(id), do: Repo.get!(TimeOption, id) + + @doc """ + Creates a time_option. + + ## Examples + + iex> create_time_option(%{field: value}) + {:ok, %TimeOption{}} + + iex> create_time_option(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_time_option(attrs \\ %{}) do + %TimeOption{} + |> TimeOption.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a time_option. + + ## Examples + + iex> update_time_option(time_option, %{field: new_value}) + {:ok, %TimeOption{}} + + iex> update_time_option(time_option, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_time_option(%TimeOption{} = time_option, attrs) do + time_option + |> TimeOption.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a time_option. + + ## Examples + + iex> delete_time_option(time_option) + {:ok, %TimeOption{}} + + iex> delete_time_option(time_option) + {:error, %Ecto.Changeset{}} + + """ + def delete_time_option(%TimeOption{} = time_option) do + Repo.delete(time_option) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking time_option changes. + + ## Examples + + iex> change_time_option(time_option) + %Ecto.Changeset{data: %TimeOption{}} + + """ + def change_time_option(%TimeOption{} = time_option, attrs \\ %{}) do + TimeOption.changeset(time_option, attrs) + end end diff --git a/lib/cadet/notifications/time_option.ex b/lib/cadet/notifications/time_option.ex new file mode 100644 index 000000000..c17010c4a --- /dev/null +++ b/lib/cadet/notifications/time_option.ex @@ -0,0 +1,21 @@ +defmodule Cadet.Notifications.TimeOption do + use Ecto.Schema + import Ecto.Changeset + alias Cadet.Notifications.NotificationConfig + + schema "time_options" do + field(:is_default, :boolean, default: false) + field(:minutes, :integer) + + belongs_to(:notification_config, NotificationConfig) + + timestamps() + end + + @doc false + def changeset(time_option, attrs) do + time_option + |> cast(attrs, [:minutes, :is_default]) + |> validate_required([:minutes, :is_default]) + end +end diff --git a/lib/cadet_web/controllers/time_option_controller.ex b/lib/cadet_web/controllers/time_option_controller.ex new file mode 100644 index 000000000..e509ccdbf --- /dev/null +++ b/lib/cadet_web/controllers/time_option_controller.ex @@ -0,0 +1,45 @@ +defmodule CadetWeb.TimeOptionController do + use CadetWeb, :controller + + alias Cadet.Notifications + alias Cadet.Notifications.TimeOption + + # action_fallback CadetWeb.FallbackController + + def index(conn, _params) do + time_options = Notifications.list_time_options() + render(conn, "index.json", time_options: time_options) + end + + def create(conn, %{"time_option" => time_option_params}) do + with {:ok, %TimeOption{} = time_option} <- + Notifications.create_time_option(time_option_params) do + conn + |> put_status(:created) + # |> put_resp_header("location", Routes.time_option_path(conn, :show, time_option)) + |> render("show.json", time_option: time_option) + end + end + + def show(conn, %{"id" => id}) do + time_option = Notifications.get_time_option!(id) + render(conn, "show.json", time_option: time_option) + end + + def update(conn, %{"id" => id, "time_option" => time_option_params}) do + time_option = Notifications.get_time_option!(id) + + with {:ok, %TimeOption{} = time_option} <- + Notifications.update_time_option(time_option, time_option_params) do + render(conn, "show.json", time_option: time_option) + end + end + + def delete(conn, %{"id" => id}) do + time_option = Notifications.get_time_option!(id) + + with {:ok, %TimeOption{}} <- Notifications.delete_time_option(time_option) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/cadet_web/views/time_option_view.ex b/lib/cadet_web/views/time_option_view.ex new file mode 100644 index 000000000..558864a25 --- /dev/null +++ b/lib/cadet_web/views/time_option_view.ex @@ -0,0 +1,20 @@ +defmodule CadetWeb.TimeOptionView do + use CadetWeb, :view + alias CadetWeb.TimeOptionView + + def render("index.json", %{time_options: time_options}) do + %{data: render_many(time_options, TimeOptionView, "time_option.json")} + end + + def render("show.json", %{time_option: time_option}) do + %{data: render_one(time_option, TimeOptionView, "time_option.json")} + end + + def render("time_option.json", %{time_option: time_option}) do + %{ + id: time_option.id, + minutes: time_option.minutes, + is_default: time_option.is_default + } + end +end diff --git a/priv/repo/migrations/20230214132717_create_time_options.exs b/priv/repo/migrations/20230214132717_create_time_options.exs new file mode 100644 index 000000000..bd821b69e --- /dev/null +++ b/priv/repo/migrations/20230214132717_create_time_options.exs @@ -0,0 +1,13 @@ +defmodule Cadet.Repo.Migrations.CreateTimeOptions do + use Ecto.Migration + + def change do + create table(:time_options) do + add(:minutes, :integer) + add(:is_default, :boolean, default: false, null: false) + add(:notification_config_id, references(:notification_configs, on_delete: :delete_all)) + + timestamps() + end + end +end diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 35b138ba3..49cb256ff 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -4,7 +4,7 @@ defmodule Cadet.NotificationsTest do alias Cadet.Notifications describe "notification_types" do - alias Cadet.Notifications.NotificationType + # alias Cadet.Notifications.NotificationType # import Cadet.NotificationsFixtures @@ -116,4 +116,60 @@ defmodule Cadet.NotificationsTest do # assert %Ecto.Changeset{} = Notifications.change_notification_config(notification_config) # end end + + describe "time_options" do + # alias Cadet.Notifications.TimeOption + + # import Cadet.NotificationsFixtures + + # @invalid_attrs %{is_default: nil, minutes: nil} + + # test "list_time_options/0 returns all time_options" do + # time_option = time_option_fixture() + # assert Notifications.list_time_options() == [time_option] + # end + + # test "get_time_option!/1 returns the time_option with given id" do + # time_option = time_option_fixture() + # assert Notifications.get_time_option!(time_option.id) == time_option + # end + + # test "create_time_option/1 with valid data creates a time_option" do + # valid_attrs = %{is_default: true, minutes: 42} + + # assert {:ok, %TimeOption{} = time_option} = Notifications.create_time_option(valid_attrs) + # assert time_option.is_default == true + # assert time_option.minutes == 42 + # end + + # test "create_time_option/1 with invalid data returns error changeset" do + # assert {:error, %Ecto.Changeset{}} = Notifications.create_time_option(@invalid_attrs) + # end + + # test "update_time_option/2 with valid data updates the time_option" do + # time_option = time_option_fixture() + # update_attrs = %{is_default: false, minutes: 43} + + # assert {:ok, %TimeOption{} = time_option} = Notifications.update_time_option(time_option, update_attrs) + # assert time_option.is_default == false + # assert time_option.minutes == 43 + # end + + # test "update_time_option/2 with invalid data returns error changeset" do + # time_option = time_option_fixture() + # assert {:error, %Ecto.Changeset{}} = Notifications.update_time_option(time_option, @invalid_attrs) + # assert time_option == Notifications.get_time_option!(time_option.id) + # end + + # test "delete_time_option/1 deletes the time_option" do + # time_option = time_option_fixture() + # assert {:ok, %TimeOption{}} = Notifications.delete_time_option(time_option) + # assert_raise Ecto.NoResultsError, fn -> Notifications.get_time_option!(time_option.id) end + # end + + # test "change_time_option/1 returns a time_option changeset" do + # time_option = time_option_fixture() + # assert %Ecto.Changeset{} = Notifications.change_time_option(time_option) + # end + end end diff --git a/test/cadet_web/controllers/time_option_controller_test.exs b/test/cadet_web/controllers/time_option_controller_test.exs new file mode 100644 index 000000000..f43624a3b --- /dev/null +++ b/test/cadet_web/controllers/time_option_controller_test.exs @@ -0,0 +1,88 @@ +defmodule CadetWeb.TimeOptionControllerTest do + use CadetWeb.ConnCase + + # import Cadet.NotificationsFixtures + + # alias Cadet.Notifications.TimeOption + + # @create_attrs %{ + # is_default: true, + # minutes: 42 + # } + # @update_attrs %{ + # is_default: false, + # minutes: 43 + # } + # @invalid_attrs %{is_default: nil, minutes: nil} + + # setup %{conn: conn} do + # {:ok, conn: put_req_header(conn, "accept", "application/json")} + # end + + # describe "index" do + # test "lists all time_options", %{conn: conn} do + # conn = get(conn, Routes.time_option_path(conn, :index)) + # assert json_response(conn, 200)["data"] == [] + # end + # end + + # describe "create time_option" do + # test "renders time_option when data is valid", %{conn: conn} do + # conn = post(conn, Routes.time_option_path(conn, :create), time_option: @create_attrs) + # assert %{"id" => id} = json_response(conn, 201)["data"] + + # conn = get(conn, Routes.time_option_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_default" => true, + # "minutes" => 42 + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn} do + # conn = post(conn, Routes.time_option_path(conn, :create), time_option: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "update time_option" do + # setup [:create_time_option] + + # test "renders time_option when data is valid", %{conn: conn, time_option: %TimeOption{id: id} = time_option} do + # conn = put(conn, Routes.time_option_path(conn, :update, time_option), time_option: @update_attrs) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.time_option_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_default" => false, + # "minutes" => 43 + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn, time_option: time_option} do + # conn = put(conn, Routes.time_option_path(conn, :update, time_option), time_option: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "delete time_option" do + # setup [:create_time_option] + + # test "deletes chosen time_option", %{conn: conn, time_option: time_option} do + # conn = delete(conn, Routes.time_option_path(conn, :delete, time_option)) + # assert response(conn, 204) + + # assert_error_sent 404, fn -> + # get(conn, Routes.time_option_path(conn, :show, time_option)) + # end + # end + # end + + # defp create_time_option(_) do + # time_option = time_option_fixture() + # %{time_option: time_option} + # end +end From a580719a10f8280917b59f75e8904edd1b7a6faf Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 14 Feb 2023 22:32:53 +0800 Subject: [PATCH 05/80] Create NotificationPreference Model --- lib/cadet/notifications.ex | 96 +++++++++++++++++++ .../notifications/notification_preference.ex | 24 +++++ .../notification_preference_controller.ex | 49 ++++++++++ .../views/notification_preference_view.ex | 19 ++++ ...140555_create_notification_preferences.exs | 14 +++ .../notifications/notifications_test.exs | 54 +++++++++++ ...otification_preference_controller_test.exs | 84 ++++++++++++++++ 7 files changed, 340 insertions(+) create mode 100644 lib/cadet/notifications/notification_preference.ex create mode 100644 lib/cadet_web/controllers/notification_preference_controller.ex create mode 100644 lib/cadet_web/views/notification_preference_view.ex create mode 100644 priv/repo/migrations/20230214140555_create_notification_preferences.exs create mode 100644 test/cadet_web/controllers/notification_preference_controller_test.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index af1b6ce15..cf52617b4 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -292,4 +292,100 @@ defmodule Cadet.Notifications do def change_time_option(%TimeOption{} = time_option, attrs \\ %{}) do TimeOption.changeset(time_option, attrs) end + + alias Cadet.Notifications.NotificationPreference + + @doc """ + Returns the list of notification_preferences. + + ## Examples + + iex> list_notification_preferences() + [%NotificationPreference{}, ...] + + """ + def list_notification_preferences do + Repo.all(NotificationPreference) + end + + @doc """ + Gets a single notification_preference. + + Raises `Ecto.NoResultsError` if the Notification preference does not exist. + + ## Examples + + iex> get_notification_preference!(123) + %NotificationPreference{} + + iex> get_notification_preference!(456) + ** (Ecto.NoResultsError) + + """ + def get_notification_preference!(id), do: Repo.get!(NotificationPreference, id) + + @doc """ + Creates a notification_preference. + + ## Examples + + iex> create_notification_preference(%{field: value}) + {:ok, %NotificationPreference{}} + + iex> create_notification_preference(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_notification_preference(attrs \\ %{}) do + %NotificationPreference{} + |> NotificationPreference.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a notification_preference. + + ## Examples + + iex> update_notification_preference(notification_preference, %{field: new_value}) + {:ok, %NotificationPreference{}} + + iex> update_notification_preference(notification_preference, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_notification_preference(%NotificationPreference{} = notification_preference, attrs) do + notification_preference + |> NotificationPreference.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a notification_preference. + + ## Examples + + iex> delete_notification_preference(notification_preference) + {:ok, %NotificationPreference{}} + + iex> delete_notification_preference(notification_preference) + {:error, %Ecto.Changeset{}} + + """ + def delete_notification_preference(%NotificationPreference{} = notification_preference) do + Repo.delete(notification_preference) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking notification_preference changes. + + ## Examples + + iex> change_notification_preference(notification_preference) + %Ecto.Changeset{data: %NotificationPreference{}} + + """ + def change_notification_preference(%NotificationPreference{} = notification_preference, attrs \\ %{}) do + NotificationPreference.changeset(notification_preference, attrs) + end end diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex new file mode 100644 index 000000000..3204610f3 --- /dev/null +++ b/lib/cadet/notifications/notification_preference.ex @@ -0,0 +1,24 @@ +defmodule Cadet.Notifications.NotificationPreference do + use Ecto.Schema + import Ecto.Changeset + alias Cadet.Notifications.NotificationConfig + alias Cadet.Notifications.TimeOption + alias Cadet.Accounts.CourseRegistration + + schema "notification_preferences" do + field(:is_enable, :boolean, default: false) + + belongs_to(:notification_config, NotificationConfig) + belongs_to(:time_option, TimeOption) + belongs_to(:course_reg, CourseRegistration) + + timestamps() + end + + @doc false + def changeset(notification_preference, attrs) do + notification_preference + |> cast(attrs, [:is_enable]) + |> validate_required([:is_enable]) + end +end diff --git a/lib/cadet_web/controllers/notification_preference_controller.ex b/lib/cadet_web/controllers/notification_preference_controller.ex new file mode 100644 index 000000000..700ed636b --- /dev/null +++ b/lib/cadet_web/controllers/notification_preference_controller.ex @@ -0,0 +1,49 @@ +defmodule CadetWeb.NotificationPreferenceController do + use CadetWeb, :controller + + alias Cadet.Notifications + alias Cadet.Notifications.NotificationPreference + + # action_fallback CadetWeb.FallbackController + + def index(conn, _params) do + notification_preferences = Notifications.list_notification_preferences() + render(conn, "index.json", notification_preferences: notification_preferences) + end + + def create(conn, %{"notification_preference" => notification_preference_params}) do + with {:ok, %NotificationPreference{} = notification_preference} <- + Notifications.create_notification_preference(notification_preference_params) do + conn + |> put_status(:created) + # |> put_resp_header("location", Routes.notification_preference_path(conn, :show, notification_preference)) + |> render("show.json", notification_preference: notification_preference) + end + end + + def show(conn, %{"id" => id}) do + notification_preference = Notifications.get_notification_preference!(id) + render(conn, "show.json", notification_preference: notification_preference) + end + + def update(conn, %{"id" => id, "notification_preference" => notification_preference_params}) do + notification_preference = Notifications.get_notification_preference!(id) + + with {:ok, %NotificationPreference{} = notification_preference} <- + Notifications.update_notification_preference( + notification_preference, + notification_preference_params + ) do + render(conn, "show.json", notification_preference: notification_preference) + end + end + + def delete(conn, %{"id" => id}) do + notification_preference = Notifications.get_notification_preference!(id) + + with {:ok, %NotificationPreference{}} <- + Notifications.delete_notification_preference(notification_preference) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/cadet_web/views/notification_preference_view.ex b/lib/cadet_web/views/notification_preference_view.ex new file mode 100644 index 000000000..c2bac0e1d --- /dev/null +++ b/lib/cadet_web/views/notification_preference_view.ex @@ -0,0 +1,19 @@ +defmodule CadetWeb.NotificationPreferenceView do + use CadetWeb, :view + alias CadetWeb.NotificationPreferenceView + + def render("index.json", %{notification_preferences: notification_preferences}) do + %{data: render_many(notification_preferences, NotificationPreferenceView, "notification_preference.json")} + end + + def render("show.json", %{notification_preference: notification_preference}) do + %{data: render_one(notification_preference, NotificationPreferenceView, "notification_preference.json")} + end + + def render("notification_preference.json", %{notification_preference: notification_preference}) do + %{ + id: notification_preference.id, + is_enable: notification_preference.is_enable + } + end +end diff --git a/priv/repo/migrations/20230214140555_create_notification_preferences.exs b/priv/repo/migrations/20230214140555_create_notification_preferences.exs new file mode 100644 index 000000000..d2bdbeacb --- /dev/null +++ b/priv/repo/migrations/20230214140555_create_notification_preferences.exs @@ -0,0 +1,14 @@ +defmodule Cadet.Repo.Migrations.CreateNotificationPreferences do + use Ecto.Migration + + def change do + create table(:notification_preferences) do + add(:is_enable, :boolean, default: false, null: false) + add(:notification_config_id, references(:notification_configs, on_delete: :delete_all)) + add(:time_option_id, references(:time_options, on_delete: :nothing)) + add(:course_reg_id, references(:course_registrations, on_delete: :delete_all)) + + timestamps() + end + end +end diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 49cb256ff..838f299ec 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -172,4 +172,58 @@ defmodule Cadet.NotificationsTest do # assert %Ecto.Changeset{} = Notifications.change_time_option(time_option) # end end + + describe "notification_preferences" do + # alias Cadet.Notifications.NotificationPreference + + # import Cadet.NotificationsFixtures + + # @invalid_attrs %{is_enable: nil} + + # test "list_notification_preferences/0 returns all notification_preferences" do + # notification_preference = notification_preference_fixture() + # assert Notifications.list_notification_preferences() == [notification_preference] + # end + + # test "get_notification_preference!/1 returns the notification_preference with given id" do + # notification_preference = notification_preference_fixture() + # assert Notifications.get_notification_preference!(notification_preference.id) == notification_preference + # end + + # test "create_notification_preference/1 with valid data creates a notification_preference" do + # valid_attrs = %{is_enable: true} + + # assert {:ok, %NotificationPreference{} = notification_preference} = Notifications.create_notification_preference(valid_attrs) + # assert notification_preference.is_enable == true + # end + + # test "create_notification_preference/1 with invalid data returns error changeset" do + # assert {:error, %Ecto.Changeset{}} = Notifications.create_notification_preference(@invalid_attrs) + # end + + # test "update_notification_preference/2 with valid data updates the notification_preference" do + # notification_preference = notification_preference_fixture() + # update_attrs = %{is_enable: false} + + # assert {:ok, %NotificationPreference{} = notification_preference} = Notifications.update_notification_preference(notification_preference, update_attrs) + # assert notification_preference.is_enable == false + # end + + # test "update_notification_preference/2 with invalid data returns error changeset" do + # notification_preference = notification_preference_fixture() + # assert {:error, %Ecto.Changeset{}} = Notifications.update_notification_preference(notification_preference, @invalid_attrs) + # assert notification_preference == Notifications.get_notification_preference!(notification_preference.id) + # end + + # test "delete_notification_preference/1 deletes the notification_preference" do + # notification_preference = notification_preference_fixture() + # assert {:ok, %NotificationPreference{}} = Notifications.delete_notification_preference(notification_preference) + # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_preference!(notification_preference.id) end + # end + + # test "change_notification_preference/1 returns a notification_preference changeset" do + # notification_preference = notification_preference_fixture() + # assert %Ecto.Changeset{} = Notifications.change_notification_preference(notification_preference) + # end + end end diff --git a/test/cadet_web/controllers/notification_preference_controller_test.exs b/test/cadet_web/controllers/notification_preference_controller_test.exs new file mode 100644 index 000000000..1a29dec48 --- /dev/null +++ b/test/cadet_web/controllers/notification_preference_controller_test.exs @@ -0,0 +1,84 @@ +defmodule CadetWeb.NotificationPreferenceControllerTest do + use CadetWeb.ConnCase + + # import Cadet.NotificationsFixtures + + # alias Cadet.Notifications.NotificationPreference + + # @create_attrs %{ + # is_enable: true + # } + # @update_attrs %{ + # is_enable: false + # } + # @invalid_attrs %{is_enable: nil} + + # setup %{conn: conn} do + # {:ok, conn: put_req_header(conn, "accept", "application/json")} + # end + + # describe "index" do + # test "lists all notification_preferences", %{conn: conn} do + # conn = get(conn, Routes.notification_preference_path(conn, :index)) + # assert json_response(conn, 200)["data"] == [] + # end + # end + + # describe "create notification_preference" do + # test "renders notification_preference when data is valid", %{conn: conn} do + # conn = post(conn, Routes.notification_preference_path(conn, :create), notification_preference: @create_attrs) + # assert %{"id" => id} = json_response(conn, 201)["data"] + + # conn = get(conn, Routes.notification_preference_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_enable" => true + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn} do + # conn = post(conn, Routes.notification_preference_path(conn, :create), notification_preference: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "update notification_preference" do + # setup [:create_notification_preference] + + # test "renders notification_preference when data is valid", %{conn: conn, notification_preference: %NotificationPreference{id: id} = notification_preference} do + # conn = put(conn, Routes.notification_preference_path(conn, :update, notification_preference), notification_preference: @update_attrs) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.notification_preference_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_enable" => false + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn, notification_preference: notification_preference} do + # conn = put(conn, Routes.notification_preference_path(conn, :update, notification_preference), notification_preference: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "delete notification_preference" do + # setup [:create_notification_preference] + + # test "deletes chosen notification_preference", %{conn: conn, notification_preference: notification_preference} do + # conn = delete(conn, Routes.notification_preference_path(conn, :delete, notification_preference)) + # assert response(conn, 204) + + # assert_error_sent 404, fn -> + # get(conn, Routes.notification_preference_path(conn, :show, notification_preference)) + # end + # end + # end + + # defp create_notification_preference(_) do + # notification_preference = notification_preference_fixture() + # %{notification_preference: notification_preference} + # end +end From 877220a894a291ed7daf55d170bd7cfb18c96815 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 14 Feb 2023 22:46:53 +0800 Subject: [PATCH 06/80] Create SentNotification Model --- lib/cadet/notifications.ex | 96 +++++++++++++++++++ lib/cadet/notifications/sent_notification.ex | 20 ++++ .../sent_notification_controller.ex | 45 +++++++++ lib/cadet_web/views/sent_notification_view.ex | 19 ++++ ...230214143617_create_sent_notifications.exs | 14 +++ .../notifications/notifications_test.exs | 54 +++++++++++ .../sent_notification_controller_test.exs | 84 ++++++++++++++++ 7 files changed, 332 insertions(+) create mode 100644 lib/cadet/notifications/sent_notification.ex create mode 100644 lib/cadet_web/controllers/sent_notification_controller.ex create mode 100644 lib/cadet_web/views/sent_notification_view.ex create mode 100644 priv/repo/migrations/20230214143617_create_sent_notifications.exs create mode 100644 test/cadet_web/controllers/sent_notification_controller_test.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index cf52617b4..ffc8b7eb4 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -388,4 +388,100 @@ defmodule Cadet.Notifications do def change_notification_preference(%NotificationPreference{} = notification_preference, attrs \\ %{}) do NotificationPreference.changeset(notification_preference, attrs) end + + alias Cadet.Notifications.SentNotification + + @doc """ + Returns the list of sent_notifications. + + ## Examples + + iex> list_sent_notifications() + [%SentNotification{}, ...] + + """ + def list_sent_notifications do + Repo.all(SentNotification) + end + + @doc """ + Gets a single sent_notification. + + Raises `Ecto.NoResultsError` if the Sent notification does not exist. + + ## Examples + + iex> get_sent_notification!(123) + %SentNotification{} + + iex> get_sent_notification!(456) + ** (Ecto.NoResultsError) + + """ + def get_sent_notification!(id), do: Repo.get!(SentNotification, id) + + @doc """ + Creates a sent_notification. + + ## Examples + + iex> create_sent_notification(%{field: value}) + {:ok, %SentNotification{}} + + iex> create_sent_notification(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_sent_notification(attrs \\ %{}) do + %SentNotification{} + |> SentNotification.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a sent_notification. + + ## Examples + + iex> update_sent_notification(sent_notification, %{field: new_value}) + {:ok, %SentNotification{}} + + iex> update_sent_notification(sent_notification, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_sent_notification(%SentNotification{} = sent_notification, attrs) do + sent_notification + |> SentNotification.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a sent_notification. + + ## Examples + + iex> delete_sent_notification(sent_notification) + {:ok, %SentNotification{}} + + iex> delete_sent_notification(sent_notification) + {:error, %Ecto.Changeset{}} + + """ + def delete_sent_notification(%SentNotification{} = sent_notification) do + Repo.delete(sent_notification) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking sent_notification changes. + + ## Examples + + iex> change_sent_notification(sent_notification) + %Ecto.Changeset{data: %SentNotification{}} + + """ + def change_sent_notification(%SentNotification{} = sent_notification, attrs \\ %{}) do + SentNotification.changeset(sent_notification, attrs) + end end diff --git a/lib/cadet/notifications/sent_notification.ex b/lib/cadet/notifications/sent_notification.ex new file mode 100644 index 000000000..859b13e2d --- /dev/null +++ b/lib/cadet/notifications/sent_notification.ex @@ -0,0 +1,20 @@ +defmodule Cadet.Notifications.SentNotification do + use Ecto.Schema + import Ecto.Changeset + alias Cadet.Accounts.CourseRegistration + + schema "sent_notifications" do + field(:content, :string) + + belongs_to(:course_reg, CourseRegistration) + + timestamps() + end + + @doc false + def changeset(sent_notification, attrs) do + sent_notification + |> cast(attrs, [:content]) + |> validate_required([:content]) + end +end diff --git a/lib/cadet_web/controllers/sent_notification_controller.ex b/lib/cadet_web/controllers/sent_notification_controller.ex new file mode 100644 index 000000000..9aba18bae --- /dev/null +++ b/lib/cadet_web/controllers/sent_notification_controller.ex @@ -0,0 +1,45 @@ +defmodule CadetWeb.SentNotificationController do + use CadetWeb, :controller + + alias Cadet.Notifications + alias Cadet.Notifications.SentNotification + + # action_fallback CadetWeb.FallbackController + + def index(conn, _params) do + sent_notifications = Notifications.list_sent_notifications() + render(conn, "index.json", sent_notifications: sent_notifications) + end + + def create(conn, %{"sent_notification" => sent_notification_params}) do + with {:ok, %SentNotification{} = sent_notification} <- + Notifications.create_sent_notification(sent_notification_params) do + conn + |> put_status(:created) + # |> put_resp_header("location", Routes.sent_notification_path(conn, :show, sent_notification)) + |> render("show.json", sent_notification: sent_notification) + end + end + + def show(conn, %{"id" => id}) do + sent_notification = Notifications.get_sent_notification!(id) + render(conn, "show.json", sent_notification: sent_notification) + end + + def update(conn, %{"id" => id, "sent_notification" => sent_notification_params}) do + sent_notification = Notifications.get_sent_notification!(id) + + with {:ok, %SentNotification{} = sent_notification} <- + Notifications.update_sent_notification(sent_notification, sent_notification_params) do + render(conn, "show.json", sent_notification: sent_notification) + end + end + + def delete(conn, %{"id" => id}) do + sent_notification = Notifications.get_sent_notification!(id) + + with {:ok, %SentNotification{}} <- Notifications.delete_sent_notification(sent_notification) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/cadet_web/views/sent_notification_view.ex b/lib/cadet_web/views/sent_notification_view.ex new file mode 100644 index 000000000..b0e3d101e --- /dev/null +++ b/lib/cadet_web/views/sent_notification_view.ex @@ -0,0 +1,19 @@ +defmodule CadetWeb.SentNotificationView do + use CadetWeb, :view + alias CadetWeb.SentNotificationView + + def render("index.json", %{sent_notifications: sent_notifications}) do + %{data: render_many(sent_notifications, SentNotificationView, "sent_notification.json")} + end + + def render("show.json", %{sent_notification: sent_notification}) do + %{data: render_one(sent_notification, SentNotificationView, "sent_notification.json")} + end + + def render("sent_notification.json", %{sent_notification: sent_notification}) do + %{ + id: sent_notification.id, + content: sent_notification.content + } + end +end diff --git a/priv/repo/migrations/20230214143617_create_sent_notifications.exs b/priv/repo/migrations/20230214143617_create_sent_notifications.exs new file mode 100644 index 000000000..0de67235a --- /dev/null +++ b/priv/repo/migrations/20230214143617_create_sent_notifications.exs @@ -0,0 +1,14 @@ +defmodule Cadet.Repo.Migrations.CreateSentNotifications do + use Ecto.Migration + + def change do + create table(:sent_notifications) do + add :content, :text + add :course_reg_id, references(:course_registrations, on_delete: :nothing) + + timestamps() + end + + create index(:sent_notifications, [:course_reg_id]) + end +end diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 838f299ec..8082555e9 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -226,4 +226,58 @@ defmodule Cadet.NotificationsTest do # assert %Ecto.Changeset{} = Notifications.change_notification_preference(notification_preference) # end end + + describe "sent_notifications" do + # alias Cadet.Notifications.SentNotification + + # import Cadet.NotificationsFixtures + + # @invalid_attrs %{content: nil} + + # test "list_sent_notifications/0 returns all sent_notifications" do + # sent_notification = sent_notification_fixture() + # assert Notifications.list_sent_notifications() == [sent_notification] + # end + + # test "get_sent_notification!/1 returns the sent_notification with given id" do + # sent_notification = sent_notification_fixture() + # assert Notifications.get_sent_notification!(sent_notification.id) == sent_notification + # end + + # test "create_sent_notification/1 with valid data creates a sent_notification" do + # valid_attrs = %{content: "some content"} + + # assert {:ok, %SentNotification{} = sent_notification} = Notifications.create_sent_notification(valid_attrs) + # assert sent_notification.content == "some content" + # end + + # test "create_sent_notification/1 with invalid data returns error changeset" do + # assert {:error, %Ecto.Changeset{}} = Notifications.create_sent_notification(@invalid_attrs) + # end + + # test "update_sent_notification/2 with valid data updates the sent_notification" do + # sent_notification = sent_notification_fixture() + # update_attrs = %{content: "some updated content"} + + # assert {:ok, %SentNotification{} = sent_notification} = Notifications.update_sent_notification(sent_notification, update_attrs) + # assert sent_notification.content == "some updated content" + # end + + # test "update_sent_notification/2 with invalid data returns error changeset" do + # sent_notification = sent_notification_fixture() + # assert {:error, %Ecto.Changeset{}} = Notifications.update_sent_notification(sent_notification, @invalid_attrs) + # assert sent_notification == Notifications.get_sent_notification!(sent_notification.id) + # end + + # test "delete_sent_notification/1 deletes the sent_notification" do + # sent_notification = sent_notification_fixture() + # assert {:ok, %SentNotification{}} = Notifications.delete_sent_notification(sent_notification) + # assert_raise Ecto.NoResultsError, fn -> Notifications.get_sent_notification!(sent_notification.id) end + # end + + # test "change_sent_notification/1 returns a sent_notification changeset" do + # sent_notification = sent_notification_fixture() + # assert %Ecto.Changeset{} = Notifications.change_sent_notification(sent_notification) + # end + end end diff --git a/test/cadet_web/controllers/sent_notification_controller_test.exs b/test/cadet_web/controllers/sent_notification_controller_test.exs new file mode 100644 index 000000000..fd5bcaab6 --- /dev/null +++ b/test/cadet_web/controllers/sent_notification_controller_test.exs @@ -0,0 +1,84 @@ +defmodule CadetWeb.SentNotificationControllerTest do + use CadetWeb.ConnCase + + # import Cadet.NotificationsFixtures + + # alias Cadet.Notifications.SentNotification + + # @create_attrs %{ + # content: "some content" + # } + # @update_attrs %{ + # content: "some updated content" + # } + # @invalid_attrs %{content: nil} + + # setup %{conn: conn} do + # {:ok, conn: put_req_header(conn, "accept", "application/json")} + # end + + # describe "index" do + # test "lists all sent_notifications", %{conn: conn} do + # conn = get(conn, Routes.sent_notification_path(conn, :index)) + # assert json_response(conn, 200)["data"] == [] + # end + # end + + # describe "create sent_notification" do + # test "renders sent_notification when data is valid", %{conn: conn} do + # conn = post(conn, Routes.sent_notification_path(conn, :create), sent_notification: @create_attrs) + # assert %{"id" => id} = json_response(conn, 201)["data"] + + # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "content" => "some content" + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn} do + # conn = post(conn, Routes.sent_notification_path(conn, :create), sent_notification: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "update sent_notification" do + # setup [:create_sent_notification] + + # test "renders sent_notification when data is valid", %{conn: conn, sent_notification: %SentNotification{id: id} = sent_notification} do + # conn = put(conn, Routes.sent_notification_path(conn, :update, sent_notification), sent_notification: @update_attrs) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "content" => "some updated content" + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{conn: conn, sent_notification: sent_notification} do + # conn = put(conn, Routes.sent_notification_path(conn, :update, sent_notification), sent_notification: @invalid_attrs) + # assert json_response(conn, 422)["errors"] != %{} + # end + # end + + # describe "delete sent_notification" do + # setup [:create_sent_notification] + + # test "deletes chosen sent_notification", %{conn: conn, sent_notification: sent_notification} do + # conn = delete(conn, Routes.sent_notification_path(conn, :delete, sent_notification)) + # assert response(conn, 204) + + # assert_error_sent 404, fn -> + # get(conn, Routes.sent_notification_path(conn, :show, sent_notification)) + # end + # end + # end + + # defp create_sent_notification(_) do + # sent_notification = sent_notification_fixture() + # %{sent_notification: sent_notification} + # end +end From c8ecd5d94bb6d8e9bb3a2e12baa0f3792c7a98f4 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 15 Feb 2023 13:16:40 +0800 Subject: [PATCH 07/80] Add Email Field to User Model --- lib/cadet/accounts/user.ex | 1 + .../migrations/20230215051347_users_add_email_column.exs | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 priv/repo/migrations/20230215051347_users_add_email_column.exs diff --git a/lib/cadet/accounts/user.ex b/lib/cadet/accounts/user.ex index aa1b8813b..d3f3c6026 100644 --- a/lib/cadet/accounts/user.ex +++ b/lib/cadet/accounts/user.ex @@ -20,6 +20,7 @@ defmodule Cadet.Accounts.User do field(:username, :string) field(:provider, :string) field(:super_admin, :boolean) + field(:email, :string) belongs_to(:latest_viewed_course, Course) has_many(:courses, CourseRegistration) diff --git a/priv/repo/migrations/20230215051347_users_add_email_column.exs b/priv/repo/migrations/20230215051347_users_add_email_column.exs new file mode 100644 index 000000000..857cf29e4 --- /dev/null +++ b/priv/repo/migrations/20230215051347_users_add_email_column.exs @@ -0,0 +1,9 @@ +defmodule Cadet.Repo.Migrations.UsersAddEmailColumn do + use Ecto.Migration + + def change do + alter table(:users) do + add :email, :string + end + end +end From 6a6cec7c91484b4a84b9076cb72353d893ed52f0 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 15 Feb 2023 16:02:57 +0800 Subject: [PATCH 08/80] fix: Fix Oban configuration --- lib/cadet/application.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/cadet/application.ex b/lib/cadet/application.ex index b825375bf..d31196e33 100644 --- a/lib/cadet/application.ex +++ b/lib/cadet/application.ex @@ -20,7 +20,9 @@ defmodule Cadet.Application do # Start the GuardianDB sweeper worker(Guardian.DB.Token.SweeperServer, []), # Start the Quantum scheduler - worker(Cadet.Jobs.Scheduler, []) + worker(Cadet.Jobs.Scheduler, []), + # Start the Oban instance + {Oban, Application.fetch_env!(:cadet, Oban)}, ] children = From 1b5ca22e5faf8e8862a75d7991237c33bef094f4 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 15 Feb 2023 16:04:27 +0800 Subject: [PATCH 09/80] chore: Add dependency on bamboo_phoenix for email template rendering --- mix.exs | 1 + mix.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/mix.exs b/mix.exs index c389dd800..0e7a00f3a 100644 --- a/mix.exs +++ b/mix.exs @@ -82,6 +82,7 @@ defmodule Cadet.Mixfile do {:timex, "~> 3.7"}, # notifiations system dependencies + {:phoenix_html, "~> 3.0"}, {:bamboo, "~> 2.3.0"}, {:bamboo_ses, "~> 0.3.0"}, {:bamboo_phoenix, "~> 1.0.0"}, diff --git a/mix.lock b/mix.lock index b96fb0111..1e863a766 100644 --- a/mix.lock +++ b/mix.lock @@ -66,6 +66,7 @@ "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "phoenix": {:hex, :phoenix, "1.6.10", "7a9e8348c5c62e7fd2f74a1884b88d98251f87186a430048bfbdbab3e3f46736", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "08cf70d42f61dd0ea381805bac3cddef57b7b92ade5acc6f6036aa25ecaca9a2"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.0", "bf451c71ebdaac8d2f40d3b703435e819ccfbb9ff243140ca3bd10c155f134cc", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "272c5c1533499f0132309936c619186480bafcc2246588f99a69ce85095556ef"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_swagger": {:hex, :phoenix_swagger, "0.8.3", "298d6204802409d3b0b4fc1013873839478707cf3a62532a9e10fec0e26d0e37", [:mix], [{:ex_json_schema, "~> 0.7.1", [hex: :ex_json_schema, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "3bc0fa9f5b679b8a61b90a52b2c67dd932320e9a84a6f91a4af872a0ab367337"}, "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, From 39367e4e04c3e62602a4852c023b057c5c91beec Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 15 Feb 2023 16:12:13 +0800 Subject: [PATCH 10/80] feat: Implement Oban job for avenger backlog emails --- config/config.exs | 11 ++-- lib/cadet/accounts/course_registrations.ex | 7 +++ lib/cadet/assessments/assessments.ex | 11 +++- lib/cadet/courses/courses.ex | 6 ++ lib/cadet/email.ex | 25 ++++++++ lib/cadet/mailer.ex | 3 + lib/cadet/notifications.ex | 44 +++++++++++++ lib/cadet/workers/NotificationWorker.ex | 63 +++++++++++++++++++ lib/cadet_web/router.ex | 4 ++ .../templates/email/backlog.html.eex | 9 +++ lib/cadet_web/templates/layout/email.html.eex | 7 +++ lib/cadet_web/views/email_view.ex | 3 + lib/cadet_web/views/layout_view.ex | 3 + 13 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 lib/cadet/email.ex create mode 100644 lib/cadet/mailer.ex create mode 100644 lib/cadet/workers/NotificationWorker.ex create mode 100644 lib/cadet_web/templates/email/backlog.html.eex create mode 100644 lib/cadet_web/templates/layout/email.html.eex create mode 100644 lib/cadet_web/views/email_view.ex create mode 100644 lib/cadet_web/views/layout_view.ex diff --git a/config/config.exs b/config/config.exs index ebc792408..202a25339 100644 --- a/config/config.exs +++ b/config/config.exs @@ -97,12 +97,15 @@ config :guardian, Guardian.DB, config :cadet, Oban, repo: Cadet.Repo, plugins: [ - Oban.Plugins.Pruner, + {Oban.Plugins.Pruner, max_age: 60}, # keep {Oban.Plugins.Cron, - crontab: [] + crontab: [ + {"* * * * *", Cadet.Workers.NotificationWorker, args: %{"notification_type" => "avenger_backlog"}}, + ] } ], - queues: [default: 10, mail: 50] + queues: [default: 10, notifications: 1] config :cadet, Cadet.Mailer, - adapter: Bamboo.LocalAdapter + adapter: Bamboo.LocalAdapter, + open_email_in_browser_url: "http://localhost:4000/sent_emails" diff --git a/lib/cadet/accounts/course_registrations.ex b/lib/cadet/accounts/course_registrations.ex index 56a125527..bdb6d9260 100644 --- a/lib/cadet/accounts/course_registrations.ex +++ b/lib/cadet/accounts/course_registrations.ex @@ -63,6 +63,13 @@ defmodule Cadet.Accounts.CourseRegistrations do |> Repo.all() end + def get_staffs(course_id) do + CourseRegistration + |> where(course_id: ^course_id) + |> where(role: :staff) + |> Repo.all() + end + def get_users(course_id, group_id) when is_ecto_id(group_id) and is_ecto_id(course_id) do CourseRegistration |> where([cr], cr.course_id == ^course_id) diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index a51062de0..4cc3de8d1 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -1151,7 +1151,8 @@ defmodule Cadet.Assessments do {:ok, String.t()} def all_submissions_by_grader_for_index( grader = %CourseRegistration{course_id: course_id}, - group_only \\ false + group_only \\ false, + ungraded_only \\ false ) do show_all = not group_only @@ -1161,6 +1162,11 @@ defmodule Cadet.Assessments do else: "where s.student_id in (select cr.id from course_registrations cr inner join groups g on cr.group_id = g.id where g.leader_id = $2) or s.student_id = $2" + ungraded_where = + if not ungraded_only, + do: "", + else: "where s.\"gradedCount\" < assts.\"questionCount\"" + params = if show_all, do: [course_id], else: [course_id, grader.id] # We bypass Ecto here and use a raw query to generate JSON directly from @@ -1200,7 +1206,7 @@ defmodule Cadet.Assessments do group by s.id) s inner join (select - a.id, to_json(a) as jsn + a.id, a."questionCount", to_json(a) as jsn from (select a.id, @@ -1240,6 +1246,7 @@ defmodule Cadet.Assessments do from course_registrations cr inner join users u on u.id = cr.user_id) cr) unsubmitters on s.unsubmitted_by_id = unsubmitters.id + #{ungraded_where} ) q """, params diff --git a/lib/cadet/courses/courses.ex b/lib/cadet/courses/courses.ex index e400c709c..701a9c646 100644 --- a/lib/cadet/courses/courses.ex +++ b/lib/cadet/courses/courses.ex @@ -82,6 +82,12 @@ defmodule Cadet.Courses do end end + def get_all_course_ids() do + Course + |> select([c], c.id) + |> Repo.all() + end + defp retrieve_course(course_id) when is_ecto_id(course_id) do Course |> where(id: ^course_id) diff --git a/lib/cadet/email.ex b/lib/cadet/email.ex new file mode 100644 index 000000000..b2302a7ac --- /dev/null +++ b/lib/cadet/email.ex @@ -0,0 +1,25 @@ +defmodule Cadet.Email do + use Bamboo.Phoenix, view: CadetWeb.EmailView + import Bamboo.Email + alias Cadet.Mailer + + def avenger_backlog_email(avenger, ungraded_submissions) do + cond do + is_nil(avenger.email) -> nil + true -> + base_email() + |> to(avenger.email) + |> assign(:avenger_name, avenger.name) + |> assign(:submissions, ungraded_submissions) + |> subject("Backlog for #{avenger.name}") + |> render("backlog.html") + |> Mailer.deliver_now() + end + end + + defp base_email() do + new_email() + |> from("noreply@sourceacademy.org") + |> put_html_layout({CadetWeb.LayoutView, "email.html"}) + end +end diff --git a/lib/cadet/mailer.ex b/lib/cadet/mailer.ex new file mode 100644 index 000000000..aded6c32d --- /dev/null +++ b/lib/cadet/mailer.ex @@ -0,0 +1,3 @@ +defmodule Cadet.Mailer do + use Bamboo.Mailer, otp_app: :cadet +end diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index ffc8b7eb4..7c7a54938 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -132,6 +132,20 @@ defmodule Cadet.Notifications do """ def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) + def get_notification_config!(notification_type_id, course_id, assconfig_id) do + query = from n in Cadet.Notifications.NotificationConfig, + join: ntype in Cadet.Notifications.NotificationType, on: n.notification_type_id == ntype.id, + where: n.notification_type_id == ^notification_type_id and n.course_id == ^course_id + + query = if is_nil(assconfig_id) do + where(query, [c], is_nil(c.assessment_config_id)) + else + where(query, [c], c.assessment_config_id == ^assconfig_id) + end + + Repo.one!(query) + end + @doc """ Creates a notification_config. @@ -228,6 +242,26 @@ defmodule Cadet.Notifications do """ def get_time_option!(id), do: Repo.get!(TimeOption, id) + def get_time_options_for_assessment(assessment_config_id, notification_type_id) do + query = from ac in Cadet.Courses.AssessmentConfig, + join: n in Cadet.Notifications.NotificationConfig, on: n.assessment_config_id == ac.id, + join: to in Cadet.Notifications.TimeOption, on: to.notification_config_id == n.id, + where: ac.id == ^assessment_config_id and n.notification_type_id == ^notification_type_id, + select: to + + Repo.all(query) + end + + def get_default_time_option_for_assessment!(assessment_config_id, notification_type_id) do + query = from ac in Cadet.Courses.AssessmentConfig, + join: n in Cadet.Notifications.NotificationConfig, on: n.assessment_config_id == ac.id, + join: to in Cadet.Notifications.TimeOption, on: to.notification_config_id == n.id, + where: ac.id == ^assessment_config_id and n.notification_type_id == ^notification_type_id and to.is_default == true, + select: to + + Repo.one!(query) + end + @doc """ Creates a time_option. @@ -324,6 +358,16 @@ defmodule Cadet.Notifications do """ def get_notification_preference!(id), do: Repo.get!(NotificationPreference, id) + def get_notification_preference(notification_type_id, course_reg_id) do + query = from np in NotificationPreference, + join: noti in Cadet.Notifications.NotificationConfig, on: np.notification_config_id == noti.id, + join: ntype in NotificationType, on: noti.notification_type_id == ntype.id, + where: ntype.id == ^notification_type_id and np.course_reg_id == ^course_reg_id, + preload: :time_option + + Repo.one(query) + end + @doc """ Creates a notification_preference. diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex new file mode 100644 index 000000000..b5cbd8f3f --- /dev/null +++ b/lib/cadet/workers/NotificationWorker.ex @@ -0,0 +1,63 @@ +defmodule Cadet.Workers.NotificationWorker do + use Oban.Worker, queue: :notifications, max_attempts: 1 + alias Cadet.Email + + defp is_system_enabled(notification_type_id) do + Cadet.Notifications.get_notification_type!(notification_type_id).is_enabled + end + + defp is_course_enabled(notification_type_id, course_id, assessment_config_id) do + Cadet.Notifications.get_notification_config!(notification_type_id, course_id, assessment_config_id).is_enabled + end + + def is_user_enabled(notification_type_id, course_reg_id) do + pref = Cadet.Notifications.get_notification_preference(notification_type_id, course_reg_id) + + cond do + (is_nil(pref)) -> true + true -> pref.is_enabled + end + end + + # Returns true if user preference matches the job's time option. + # If user has made no preference, the default time option is used instead + def is_user_time_option_matched(notification_type_id, assessment_config_id, course_reg_id, time_option_minutes) do + pref = Cadet.Notifications.get_notification_preference(notification_type_id, course_reg_id) + + cond do + (is_nil(pref)) -> + Cadet.Notifications.get_default_time_option_for_assessment!(assessment_config_id, notification_type_id).minutes == time_option_minutes + true -> + pref.time_option.minutes == time_option_minutes + end + end + + @impl Oban.Worker + def perform(%Oban.Job{ + args: %{"notification_type" => notification_type} = _args + }) + when notification_type == "avenger_backlog" do + notification_type_id = 1 + ungraded_threshold = 5 + + for course_id <- Cadet.Courses.get_all_course_ids() do + avengers_crs = Cadet.Accounts.CourseRegistrations.get_staffs(course_id) + + for avenger_cr <- avengers_crs do + avenger = Cadet.Accounts.get_user(avenger_cr.user_id) + ungraded_submissions = Jason.decode!(elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), 1)) + + cond do + length(ungraded_submissions) < ungraded_threshold -> IO.puts("[AVENGER_BACKLOG] below threshold!") + # !is_system_enabled(notification_type_id) -> IO.puts("[AVENGER_BACKLOG] system-level disabled!") + # !is_course_enabled(notification_type_id, course_id, nil) -> IO.puts("[AVENGER_BACKLOG] course-level disabled") + true -> + IO.puts("[AVENGER_BACKLOG] SENDING_OUT") + Email.avenger_backlog_email(avenger, ungraded_submissions) + end + end + end + + :ok + end +end diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 9ba04adee..542daccee 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -199,6 +199,10 @@ defmodule CadetWeb.Router do get("/", DefaultController, :index) end + if Mix.env == :dev do + forward "/sent_emails", Bamboo.SentEmailViewerPlug + end + defp assign_course(conn, _opts) do course_id = conn.path_params["course_id"] diff --git a/lib/cadet_web/templates/email/backlog.html.eex b/lib/cadet_web/templates/email/backlog.html.eex new file mode 100644 index 000000000..1bd59d74b --- /dev/null +++ b/lib/cadet_web/templates/email/backlog.html.eex @@ -0,0 +1,9 @@ +

Dear <%= @avenger_name %>,

+ +You have ungraded submissions. Please review and grade the following submissions as soon as possible. + +<%= for s <- @submissions do %> +

<%= s["assessment"]["title"] %> by <%= s["student"]["name"]%>

+<% end %> + +Unsubscribe from this email topic. diff --git a/lib/cadet_web/templates/layout/email.html.eex b/lib/cadet_web/templates/layout/email.html.eex new file mode 100644 index 000000000..b9725481e --- /dev/null +++ b/lib/cadet_web/templates/layout/email.html.eex @@ -0,0 +1,7 @@ + + + + + <%= @inner_content %> + + diff --git a/lib/cadet_web/views/email_view.ex b/lib/cadet_web/views/email_view.ex new file mode 100644 index 000000000..989a40f4f --- /dev/null +++ b/lib/cadet_web/views/email_view.ex @@ -0,0 +1,3 @@ +defmodule CadetWeb.EmailView do + use CadetWeb, :view +end diff --git a/lib/cadet_web/views/layout_view.ex b/lib/cadet_web/views/layout_view.ex new file mode 100644 index 000000000..3dfa39d84 --- /dev/null +++ b/lib/cadet_web/views/layout_view.ex @@ -0,0 +1,3 @@ +defmodule CadetWeb.LayoutView do + use CadetWeb, :view +end From e6cd06cfe09c40219533f53be9470e2794d9a626 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 15 Feb 2023 16:32:40 +0800 Subject: [PATCH 11/80] chore: Prevent browser from opening automatically when a local email is sent --- config/config.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index 202a25339..aedac38f9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -107,5 +107,4 @@ config :cadet, Oban, queues: [default: 10, notifications: 1] config :cadet, Cadet.Mailer, - adapter: Bamboo.LocalAdapter, - open_email_in_browser_url: "http://localhost:4000/sent_emails" + adapter: Bamboo.LocalAdapter From 4cc7b7fb38b73f4a7ae10fa5362527b9c22ca37a Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 15 Feb 2023 18:05:23 +0800 Subject: [PATCH 12/80] Add migrations for notification triggers and avenger backlog notification entry --- ...d_notification_configs_courses_trigger.exs | 35 +++++++++++++++++++ ...tification_configs_assessments_trigger.exs | 35 +++++++++++++++++++ ..._add_avenger_backlog_notification_type.exs | 11 ++++++ 3 files changed, 81 insertions(+) create mode 100644 priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs create mode 100644 priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs create mode 100644 priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs diff --git a/priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs b/priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs new file mode 100644 index 000000000..4a9899c6a --- /dev/null +++ b/priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs @@ -0,0 +1,35 @@ +defmodule Cadet.Repo.Migrations.AddNotificationConfigsCoursesTrigger do + use Ecto.Migration + + def up do + execute """ + CREATE OR REPLACE FUNCTION populate_noti_configs_from_notification_types_for_course() RETURNS trigger AS $$ + DECLARE + ntype Record; + BEGIN + FOR ntype IN (SELECT * FROM notification_types WHERE is_autopopulated = TRUE) LOOP + INSERT INTO notification_configs (notification_type_id, course_id, assessment_config_id, inserted_at, updated_at) + VALUES (ntype.id, NEW.id, NULL, current_timestamp, current_timestamp); + END LOOP; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + """ + + execute """ + CREATE TRIGGER populate_notification_configs_on_new_course + AFTER INSERT ON courses + FOR EACH ROW EXECUTE PROCEDURE populate_noti_configs_from_notification_types_for_course(); + """ + end + + def down do + execute """ + DROP TRIGGER IF EXISTS populate_notification_configs_on_new_course ON courses; + """ + + execute """ + DROP FUNCTION IF EXISTS populate_noti_configs_from_notification_types_for_course; + """ + end +end diff --git a/priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs b/priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs new file mode 100644 index 000000000..c546c8762 --- /dev/null +++ b/priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs @@ -0,0 +1,35 @@ +defmodule Cadet.Repo.Migrations.AddNotificationConfigsAssessmentsTrigger do + use Ecto.Migration + + def up do + execute """ + CREATE OR REPLACE FUNCTION populate_noti_configs_from_notification_types_for_assconf() RETURNS trigger AS $$ + DECLARE + ntype Record; + BEGIN + FOR ntype IN (SELECT * FROM notification_types WHERE is_autopopulated = FALSE) LOOP + INSERT INTO notification_configs (notification_type_id, course_id, assessment_config_id, inserted_at, updated_at) + VALUES (ntype.id, NEW.course_id, NEW.id, current_timestamp, current_timestamp); + END LOOP; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + """ + + execute """ + CREATE TRIGGER populate_notification_configs_on_new_assessment_config + AFTER INSERT ON assessment_configs + FOR EACH ROW EXECUTE PROCEDURE populate_noti_configs_from_notification_types_for_assconf(); + """ + end + + def down do + execute """ + DROP TRIGGER IF EXISTS populate_notification_configs_on_new_assessment_config ON assessment_configs; + """ + + execute """ + DROP FUNCTION IF EXISTS populate_noti_configs_from_notification_types_for_assconf; + """ + end +end diff --git a/priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs b/priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs new file mode 100644 index 000000000..bc24e584c --- /dev/null +++ b/priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs @@ -0,0 +1,11 @@ +defmodule Cadet.Repo.Migrations.AddAvengerBacklogNotificationType do + use Ecto.Migration + + def up do + execute "INSERT INTO notification_types (name, template_file_name, is_autopopulated, inserted_at, updated_at) VALUES ('AVENGER BACKLOG', 'avenger_backlog', TRUE, current_timestamp, current_timestamp)" + end + + def down do + execute "DELETE FROM notification_types WHERE name = 'AVENGER BACKLOG'" + end +end From 1802e49802c033732a7657cf6c7cec70921313f4 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 15 Feb 2023 18:06:08 +0800 Subject: [PATCH 13/80] Implement notification configuration checks --- config/config.exs | 2 +- lib/cadet/email.ex | 4 ++-- lib/cadet/workers/NotificationWorker.ex | 10 ++++++---- .../{backlog.html.eex => avenger_backlog.html.eex} | 0 4 files changed, 9 insertions(+), 7 deletions(-) rename lib/cadet_web/templates/email/{backlog.html.eex => avenger_backlog.html.eex} (100%) diff --git a/config/config.exs b/config/config.exs index aedac38f9..54bc19656 100644 --- a/config/config.exs +++ b/config/config.exs @@ -100,7 +100,7 @@ config :cadet, Oban, {Oban.Plugins.Pruner, max_age: 60}, # keep {Oban.Plugins.Cron, crontab: [ - {"* * * * *", Cadet.Workers.NotificationWorker, args: %{"notification_type" => "avenger_backlog"}}, + {"@daily", Cadet.Workers.NotificationWorker, args: %{"notification_type" => "avenger_backlog"}}, ] } ], diff --git a/lib/cadet/email.ex b/lib/cadet/email.ex index b2302a7ac..38be65e49 100644 --- a/lib/cadet/email.ex +++ b/lib/cadet/email.ex @@ -3,7 +3,7 @@ defmodule Cadet.Email do import Bamboo.Email alias Cadet.Mailer - def avenger_backlog_email(avenger, ungraded_submissions) do + def avenger_backlog_email(template_file_name, avenger, ungraded_submissions) do cond do is_nil(avenger.email) -> nil true -> @@ -12,7 +12,7 @@ defmodule Cadet.Email do |> assign(:avenger_name, avenger.name) |> assign(:submissions, ungraded_submissions) |> subject("Backlog for #{avenger.name}") - |> render("backlog.html") + |> render("#{template_file_name}.html") |> Mailer.deliver_now() end end diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index b5cbd8f3f..c5e0da408 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -37,9 +37,11 @@ defmodule Cadet.Workers.NotificationWorker do args: %{"notification_type" => notification_type} = _args }) when notification_type == "avenger_backlog" do - notification_type_id = 1 + notification_type_id = 2 ungraded_threshold = 5 + ntype = Cadet.Notifications.get_notification_type!(notification_type_id) + for course_id <- Cadet.Courses.get_all_course_ids() do avengers_crs = Cadet.Accounts.CourseRegistrations.get_staffs(course_id) @@ -49,11 +51,11 @@ defmodule Cadet.Workers.NotificationWorker do cond do length(ungraded_submissions) < ungraded_threshold -> IO.puts("[AVENGER_BACKLOG] below threshold!") - # !is_system_enabled(notification_type_id) -> IO.puts("[AVENGER_BACKLOG] system-level disabled!") - # !is_course_enabled(notification_type_id, course_id, nil) -> IO.puts("[AVENGER_BACKLOG] course-level disabled") + !is_system_enabled(notification_type_id) -> IO.puts("[AVENGER_BACKLOG] system-level disabled!") + !is_course_enabled(notification_type_id, course_id, nil) -> IO.puts("[AVENGER_BACKLOG] course-level disabled") true -> IO.puts("[AVENGER_BACKLOG] SENDING_OUT") - Email.avenger_backlog_email(avenger, ungraded_submissions) + Email.avenger_backlog_email(ntype.template_file_name, avenger, ungraded_submissions) end end end diff --git a/lib/cadet_web/templates/email/backlog.html.eex b/lib/cadet_web/templates/email/avenger_backlog.html.eex similarity index 100% rename from lib/cadet_web/templates/email/backlog.html.eex rename to lib/cadet_web/templates/email/avenger_backlog.html.eex From 0f7b7e45deb41e2caa93f360b90ce99683b07086 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Thu, 16 Feb 2023 11:41:36 +0800 Subject: [PATCH 14/80] Fix formatting errors --- config/config.exs | 14 ++-- config/prod.exs | 3 +- lib/cadet/application.ex | 2 +- lib/cadet/email.ex | 16 +++-- lib/cadet/notifications.ex | 71 ++++++++++++------- lib/cadet/workers/NotificationWorker.ex | 41 ++++++++--- lib/cadet_web/router.ex | 4 +- .../views/notification_preference_view.ex | 18 ++++- ...230214065925_create_notification_types.exs | 8 +-- ...230214143617_create_sent_notifications.exs | 6 +- .../20230215051347_users_add_email_column.exs | 2 +- ...d_notification_configs_courses_trigger.exs | 16 ++--- ...tification_configs_assessments_trigger.exs | 16 ++--- ..._add_avenger_backlog_notification_type.exs | 6 +- 14 files changed, 142 insertions(+), 81 deletions(-) diff --git a/config/config.exs b/config/config.exs index 54bc19656..cc752cc04 100644 --- a/config/config.exs +++ b/config/config.exs @@ -97,14 +97,14 @@ config :guardian, Guardian.DB, config :cadet, Oban, repo: Cadet.Repo, plugins: [ - {Oban.Plugins.Pruner, max_age: 60}, # keep + # keep + {Oban.Plugins.Pruner, max_age: 60}, {Oban.Plugins.Cron, - crontab: [ - {"@daily", Cadet.Workers.NotificationWorker, args: %{"notification_type" => "avenger_backlog"}}, - ] - } + crontab: [ + {"@daily", Cadet.Workers.NotificationWorker, + args: %{"notification_type" => "avenger_backlog"}} + ]} ], queues: [default: 10, notifications: 1] -config :cadet, Cadet.Mailer, - adapter: Bamboo.LocalAdapter +config :cadet, Cadet.Mailer, adapter: Bamboo.LocalAdapter diff --git a/config/prod.exs b/config/prod.exs index 6f3fc4b46..c41ba0cb1 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -45,5 +45,4 @@ config :ex_aws, access_key_id: [:instance_role], secret_access_key: [:instance_role] -config :cadet, Cadet.Mailer, - adapter: Bamboo.SesAdapter +config :cadet, Cadet.Mailer, adapter: Bamboo.SesAdapter diff --git a/lib/cadet/application.ex b/lib/cadet/application.ex index d31196e33..f2249418a 100644 --- a/lib/cadet/application.ex +++ b/lib/cadet/application.ex @@ -22,7 +22,7 @@ defmodule Cadet.Application do # Start the Quantum scheduler worker(Cadet.Jobs.Scheduler, []), # Start the Oban instance - {Oban, Application.fetch_env!(:cadet, Oban)}, + {Oban, Application.fetch_env!(:cadet, Oban)} ] children = diff --git a/lib/cadet/email.ex b/lib/cadet/email.ex index 38be65e49..dc5724557 100644 --- a/lib/cadet/email.ex +++ b/lib/cadet/email.ex @@ -5,15 +5,17 @@ defmodule Cadet.Email do def avenger_backlog_email(template_file_name, avenger, ungraded_submissions) do cond do - is_nil(avenger.email) -> nil + is_nil(avenger.email) -> + nil + true -> base_email() - |> to(avenger.email) - |> assign(:avenger_name, avenger.name) - |> assign(:submissions, ungraded_submissions) - |> subject("Backlog for #{avenger.name}") - |> render("#{template_file_name}.html") - |> Mailer.deliver_now() + |> to(avenger.email) + |> assign(:avenger_name, avenger.name) + |> assign(:submissions, ungraded_submissions) + |> subject("Backlog for #{avenger.name}") + |> render("#{template_file_name}.html") + |> Mailer.deliver_now() end end diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 7c7a54938..5bb252af9 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -133,15 +133,19 @@ defmodule Cadet.Notifications do def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) def get_notification_config!(notification_type_id, course_id, assconfig_id) do - query = from n in Cadet.Notifications.NotificationConfig, - join: ntype in Cadet.Notifications.NotificationType, on: n.notification_type_id == ntype.id, - where: n.notification_type_id == ^notification_type_id and n.course_id == ^course_id - - query = if is_nil(assconfig_id) do - where(query, [c], is_nil(c.assessment_config_id)) - else - where(query, [c], c.assessment_config_id == ^assconfig_id) - end + query = + from(n in Cadet.Notifications.NotificationConfig, + join: ntype in Cadet.Notifications.NotificationType, + on: n.notification_type_id == ntype.id, + where: n.notification_type_id == ^notification_type_id and n.course_id == ^course_id + ) + + query = + if is_nil(assconfig_id) do + where(query, [c], is_nil(c.assessment_config_id)) + else + where(query, [c], c.assessment_config_id == ^assconfig_id) + end Repo.one!(query) end @@ -243,21 +247,31 @@ defmodule Cadet.Notifications do def get_time_option!(id), do: Repo.get!(TimeOption, id) def get_time_options_for_assessment(assessment_config_id, notification_type_id) do - query = from ac in Cadet.Courses.AssessmentConfig, - join: n in Cadet.Notifications.NotificationConfig, on: n.assessment_config_id == ac.id, - join: to in Cadet.Notifications.TimeOption, on: to.notification_config_id == n.id, - where: ac.id == ^assessment_config_id and n.notification_type_id == ^notification_type_id, - select: to + query = + from(ac in Cadet.Courses.AssessmentConfig, + join: n in Cadet.Notifications.NotificationConfig, + on: n.assessment_config_id == ac.id, + join: to in Cadet.Notifications.TimeOption, + on: to.notification_config_id == n.id, + where: ac.id == ^assessment_config_id and n.notification_type_id == ^notification_type_id, + select: to + ) Repo.all(query) end def get_default_time_option_for_assessment!(assessment_config_id, notification_type_id) do - query = from ac in Cadet.Courses.AssessmentConfig, - join: n in Cadet.Notifications.NotificationConfig, on: n.assessment_config_id == ac.id, - join: to in Cadet.Notifications.TimeOption, on: to.notification_config_id == n.id, - where: ac.id == ^assessment_config_id and n.notification_type_id == ^notification_type_id and to.is_default == true, - select: to + query = + from(ac in Cadet.Courses.AssessmentConfig, + join: n in Cadet.Notifications.NotificationConfig, + on: n.assessment_config_id == ac.id, + join: to in Cadet.Notifications.TimeOption, + on: to.notification_config_id == n.id, + where: + ac.id == ^assessment_config_id and n.notification_type_id == ^notification_type_id and + to.is_default == true, + select: to + ) Repo.one!(query) end @@ -359,11 +373,15 @@ defmodule Cadet.Notifications do def get_notification_preference!(id), do: Repo.get!(NotificationPreference, id) def get_notification_preference(notification_type_id, course_reg_id) do - query = from np in NotificationPreference, - join: noti in Cadet.Notifications.NotificationConfig, on: np.notification_config_id == noti.id, - join: ntype in NotificationType, on: noti.notification_type_id == ntype.id, - where: ntype.id == ^notification_type_id and np.course_reg_id == ^course_reg_id, - preload: :time_option + query = + from(np in NotificationPreference, + join: noti in Cadet.Notifications.NotificationConfig, + on: np.notification_config_id == noti.id, + join: ntype in NotificationType, + on: noti.notification_type_id == ntype.id, + where: ntype.id == ^notification_type_id and np.course_reg_id == ^course_reg_id, + preload: :time_option + ) Repo.one(query) end @@ -429,7 +447,10 @@ defmodule Cadet.Notifications do %Ecto.Changeset{data: %NotificationPreference{}} """ - def change_notification_preference(%NotificationPreference{} = notification_preference, attrs \\ %{}) do + def change_notification_preference( + %NotificationPreference{} = notification_preference, + attrs \\ %{} + ) do NotificationPreference.changeset(notification_preference, attrs) end diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index c5e0da408..f9b04176c 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -7,26 +7,39 @@ defmodule Cadet.Workers.NotificationWorker do end defp is_course_enabled(notification_type_id, course_id, assessment_config_id) do - Cadet.Notifications.get_notification_config!(notification_type_id, course_id, assessment_config_id).is_enabled + Cadet.Notifications.get_notification_config!( + notification_type_id, + course_id, + assessment_config_id + ).is_enabled end def is_user_enabled(notification_type_id, course_reg_id) do pref = Cadet.Notifications.get_notification_preference(notification_type_id, course_reg_id) cond do - (is_nil(pref)) -> true + is_nil(pref) -> true true -> pref.is_enabled end end # Returns true if user preference matches the job's time option. # If user has made no preference, the default time option is used instead - def is_user_time_option_matched(notification_type_id, assessment_config_id, course_reg_id, time_option_minutes) do + def is_user_time_option_matched( + notification_type_id, + assessment_config_id, + course_reg_id, + time_option_minutes + ) do pref = Cadet.Notifications.get_notification_preference(notification_type_id, course_reg_id) cond do - (is_nil(pref)) -> - Cadet.Notifications.get_default_time_option_for_assessment!(assessment_config_id, notification_type_id).minutes == time_option_minutes + is_nil(pref) -> + Cadet.Notifications.get_default_time_option_for_assessment!( + assessment_config_id, + notification_type_id + ).minutes == time_option_minutes + true -> pref.time_option.minutes == time_option_minutes end @@ -47,12 +60,22 @@ defmodule Cadet.Workers.NotificationWorker do for avenger_cr <- avengers_crs do avenger = Cadet.Accounts.get_user(avenger_cr.user_id) - ungraded_submissions = Jason.decode!(elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), 1)) + + ungraded_submissions = + Jason.decode!( + elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), 1) + ) cond do - length(ungraded_submissions) < ungraded_threshold -> IO.puts("[AVENGER_BACKLOG] below threshold!") - !is_system_enabled(notification_type_id) -> IO.puts("[AVENGER_BACKLOG] system-level disabled!") - !is_course_enabled(notification_type_id, course_id, nil) -> IO.puts("[AVENGER_BACKLOG] course-level disabled") + length(ungraded_submissions) < ungraded_threshold -> + IO.puts("[AVENGER_BACKLOG] below threshold!") + + !is_system_enabled(notification_type_id) -> + IO.puts("[AVENGER_BACKLOG] system-level disabled!") + + !is_course_enabled(notification_type_id, course_id, nil) -> + IO.puts("[AVENGER_BACKLOG] course-level disabled") + true -> IO.puts("[AVENGER_BACKLOG] SENDING_OUT") Email.avenger_backlog_email(ntype.template_file_name, avenger, ungraded_submissions) diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 542daccee..cbd3fb755 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -199,8 +199,8 @@ defmodule CadetWeb.Router do get("/", DefaultController, :index) end - if Mix.env == :dev do - forward "/sent_emails", Bamboo.SentEmailViewerPlug + if Mix.env() == :dev do + forward("/sent_emails", Bamboo.SentEmailViewerPlug) end defp assign_course(conn, _opts) do diff --git a/lib/cadet_web/views/notification_preference_view.ex b/lib/cadet_web/views/notification_preference_view.ex index c2bac0e1d..a6b4db665 100644 --- a/lib/cadet_web/views/notification_preference_view.ex +++ b/lib/cadet_web/views/notification_preference_view.ex @@ -3,11 +3,25 @@ defmodule CadetWeb.NotificationPreferenceView do alias CadetWeb.NotificationPreferenceView def render("index.json", %{notification_preferences: notification_preferences}) do - %{data: render_many(notification_preferences, NotificationPreferenceView, "notification_preference.json")} + %{ + data: + render_many( + notification_preferences, + NotificationPreferenceView, + "notification_preference.json" + ) + } end def render("show.json", %{notification_preference: notification_preference}) do - %{data: render_one(notification_preference, NotificationPreferenceView, "notification_preference.json")} + %{ + data: + render_one( + notification_preference, + NotificationPreferenceView, + "notification_preference.json" + ) + } end def render("notification_preference.json", %{notification_preference: notification_preference}) do diff --git a/priv/repo/migrations/20230214065925_create_notification_types.exs b/priv/repo/migrations/20230214065925_create_notification_types.exs index f2a10a761..a9ebfada1 100644 --- a/priv/repo/migrations/20230214065925_create_notification_types.exs +++ b/priv/repo/migrations/20230214065925_create_notification_types.exs @@ -3,10 +3,10 @@ defmodule Cadet.Repo.Migrations.CreateNotificationTypes do def change do create table(:notification_types) do - add :name, :string - add :template_file_name, :string - add :is_enabled, :boolean, default: false, null: false - add :is_autopopulated, :boolean, default: false, null: false + add(:name, :string) + add(:template_file_name, :string) + add(:is_enabled, :boolean, default: false, null: false) + add(:is_autopopulated, :boolean, default: false, null: false) timestamps() end diff --git a/priv/repo/migrations/20230214143617_create_sent_notifications.exs b/priv/repo/migrations/20230214143617_create_sent_notifications.exs index 0de67235a..276dd16dd 100644 --- a/priv/repo/migrations/20230214143617_create_sent_notifications.exs +++ b/priv/repo/migrations/20230214143617_create_sent_notifications.exs @@ -3,12 +3,12 @@ defmodule Cadet.Repo.Migrations.CreateSentNotifications do def change do create table(:sent_notifications) do - add :content, :text - add :course_reg_id, references(:course_registrations, on_delete: :nothing) + add(:content, :text) + add(:course_reg_id, references(:course_registrations, on_delete: :nothing)) timestamps() end - create index(:sent_notifications, [:course_reg_id]) + create(index(:sent_notifications, [:course_reg_id])) end end diff --git a/priv/repo/migrations/20230215051347_users_add_email_column.exs b/priv/repo/migrations/20230215051347_users_add_email_column.exs index 857cf29e4..6810f8565 100644 --- a/priv/repo/migrations/20230215051347_users_add_email_column.exs +++ b/priv/repo/migrations/20230215051347_users_add_email_column.exs @@ -3,7 +3,7 @@ defmodule Cadet.Repo.Migrations.UsersAddEmailColumn do def change do alter table(:users) do - add :email, :string + add(:email, :string) end end end diff --git a/priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs b/priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs index 4a9899c6a..30aaf8a11 100644 --- a/priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs +++ b/priv/repo/migrations/20230215091253_add_notification_configs_courses_trigger.exs @@ -2,7 +2,7 @@ defmodule Cadet.Repo.Migrations.AddNotificationConfigsCoursesTrigger do use Ecto.Migration def up do - execute """ + execute(""" CREATE OR REPLACE FUNCTION populate_noti_configs_from_notification_types_for_course() RETURNS trigger AS $$ DECLARE ntype Record; @@ -14,22 +14,22 @@ defmodule Cadet.Repo.Migrations.AddNotificationConfigsCoursesTrigger do RETURN NEW; END; $$ LANGUAGE plpgsql; - """ + """) - execute """ + execute(""" CREATE TRIGGER populate_notification_configs_on_new_course AFTER INSERT ON courses FOR EACH ROW EXECUTE PROCEDURE populate_noti_configs_from_notification_types_for_course(); - """ + """) end def down do - execute """ + execute(""" DROP TRIGGER IF EXISTS populate_notification_configs_on_new_course ON courses; - """ + """) - execute """ + execute(""" DROP FUNCTION IF EXISTS populate_noti_configs_from_notification_types_for_course; - """ + """) end end diff --git a/priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs b/priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs index c546c8762..7b5b6b6aa 100644 --- a/priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs +++ b/priv/repo/migrations/20230215091948_add_notification_configs_assessments_trigger.exs @@ -2,7 +2,7 @@ defmodule Cadet.Repo.Migrations.AddNotificationConfigsAssessmentsTrigger do use Ecto.Migration def up do - execute """ + execute(""" CREATE OR REPLACE FUNCTION populate_noti_configs_from_notification_types_for_assconf() RETURNS trigger AS $$ DECLARE ntype Record; @@ -14,22 +14,22 @@ defmodule Cadet.Repo.Migrations.AddNotificationConfigsAssessmentsTrigger do RETURN NEW; END; $$ LANGUAGE plpgsql; - """ + """) - execute """ + execute(""" CREATE TRIGGER populate_notification_configs_on_new_assessment_config AFTER INSERT ON assessment_configs FOR EACH ROW EXECUTE PROCEDURE populate_noti_configs_from_notification_types_for_assconf(); - """ + """) end def down do - execute """ + execute(""" DROP TRIGGER IF EXISTS populate_notification_configs_on_new_assessment_config ON assessment_configs; - """ + """) - execute """ + execute(""" DROP FUNCTION IF EXISTS populate_noti_configs_from_notification_types_for_assconf; - """ + """) end end diff --git a/priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs b/priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs index bc24e584c..8abf000eb 100644 --- a/priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs +++ b/priv/repo/migrations/20230215092543_add_avenger_backlog_notification_type.exs @@ -2,10 +2,12 @@ defmodule Cadet.Repo.Migrations.AddAvengerBacklogNotificationType do use Ecto.Migration def up do - execute "INSERT INTO notification_types (name, template_file_name, is_autopopulated, inserted_at, updated_at) VALUES ('AVENGER BACKLOG', 'avenger_backlog', TRUE, current_timestamp, current_timestamp)" + execute( + "INSERT INTO notification_types (name, template_file_name, is_autopopulated, inserted_at, updated_at) VALUES ('AVENGER BACKLOG', 'avenger_backlog', TRUE, current_timestamp, current_timestamp)" + ) end def down do - execute "DELETE FROM notification_types WHERE name = 'AVENGER BACKLOG'" + execute("DELETE FROM notification_types WHERE name = 'AVENGER BACKLOG'") end end From dd951e86c99daef6ed323ff66ab4da31803a1237 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Tue, 21 Feb 2023 23:56:03 +0800 Subject: [PATCH 15/80] fix: Fix notifications schema typos Changes: * Replaced is_enable to is_enabled for Notification Preferences * Update default is_enabled to true for Notifcation Types --- lib/cadet/notifications/notification_preference.ex | 6 +++--- lib/cadet/notifications/notification_type.ex | 2 +- lib/cadet_web/views/notification_preference_view.ex | 2 +- .../20230214140555_create_notification_preferences.exs | 2 +- test/cadet/notifications/notifications_test.exs | 8 ++++---- .../notification_preference_controller_test.exs | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index 3204610f3..82571fb03 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -6,7 +6,7 @@ defmodule Cadet.Notifications.NotificationPreference do alias Cadet.Accounts.CourseRegistration schema "notification_preferences" do - field(:is_enable, :boolean, default: false) + field(:is_enabled, :boolean, default: false) belongs_to(:notification_config, NotificationConfig) belongs_to(:time_option, TimeOption) @@ -18,7 +18,7 @@ defmodule Cadet.Notifications.NotificationPreference do @doc false def changeset(notification_preference, attrs) do notification_preference - |> cast(attrs, [:is_enable]) - |> validate_required([:is_enable]) + |> cast(attrs, [:is_enabled]) + |> validate_required([:is_enabled]) end end diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex index 232d7af1e..aabbefeed 100644 --- a/lib/cadet/notifications/notification_type.ex +++ b/lib/cadet/notifications/notification_type.ex @@ -4,7 +4,7 @@ defmodule Cadet.Notifications.NotificationType do schema "notification_types" do field(:is_autopopulated, :boolean, default: false) - field(:is_enabled, :boolean, default: true) + field(:is_enabled, :boolean, default: false) field(:name, :string) field(:template_file_name, :string) diff --git a/lib/cadet_web/views/notification_preference_view.ex b/lib/cadet_web/views/notification_preference_view.ex index a6b4db665..dfe0db752 100644 --- a/lib/cadet_web/views/notification_preference_view.ex +++ b/lib/cadet_web/views/notification_preference_view.ex @@ -27,7 +27,7 @@ defmodule CadetWeb.NotificationPreferenceView do def render("notification_preference.json", %{notification_preference: notification_preference}) do %{ id: notification_preference.id, - is_enable: notification_preference.is_enable + is_enabled: notification_preference.is_enabled } end end diff --git a/priv/repo/migrations/20230214140555_create_notification_preferences.exs b/priv/repo/migrations/20230214140555_create_notification_preferences.exs index d2bdbeacb..155826666 100644 --- a/priv/repo/migrations/20230214140555_create_notification_preferences.exs +++ b/priv/repo/migrations/20230214140555_create_notification_preferences.exs @@ -3,7 +3,7 @@ defmodule Cadet.Repo.Migrations.CreateNotificationPreferences do def change do create table(:notification_preferences) do - add(:is_enable, :boolean, default: false, null: false) + add(:is_enabled, :boolean, default: false, null: false) add(:notification_config_id, references(:notification_configs, on_delete: :delete_all)) add(:time_option_id, references(:time_options, on_delete: :nothing)) add(:course_reg_id, references(:course_registrations, on_delete: :delete_all)) diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 8082555e9..bb6c53abc 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -191,10 +191,10 @@ defmodule Cadet.NotificationsTest do # end # test "create_notification_preference/1 with valid data creates a notification_preference" do - # valid_attrs = %{is_enable: true} + # valid_attrs = %{is_enabled: true} # assert {:ok, %NotificationPreference{} = notification_preference} = Notifications.create_notification_preference(valid_attrs) - # assert notification_preference.is_enable == true + # assert notification_preference.is_enabled == true # end # test "create_notification_preference/1 with invalid data returns error changeset" do @@ -203,10 +203,10 @@ defmodule Cadet.NotificationsTest do # test "update_notification_preference/2 with valid data updates the notification_preference" do # notification_preference = notification_preference_fixture() - # update_attrs = %{is_enable: false} + # update_attrs = %{is_enabled: false} # assert {:ok, %NotificationPreference{} = notification_preference} = Notifications.update_notification_preference(notification_preference, update_attrs) - # assert notification_preference.is_enable == false + # assert notification_preference.is_enabled == false # end # test "update_notification_preference/2 with invalid data returns error changeset" do diff --git a/test/cadet_web/controllers/notification_preference_controller_test.exs b/test/cadet_web/controllers/notification_preference_controller_test.exs index 1a29dec48..ec283437d 100644 --- a/test/cadet_web/controllers/notification_preference_controller_test.exs +++ b/test/cadet_web/controllers/notification_preference_controller_test.exs @@ -6,12 +6,12 @@ defmodule CadetWeb.NotificationPreferenceControllerTest do # alias Cadet.Notifications.NotificationPreference # @create_attrs %{ - # is_enable: true + # is_enabled: true # } # @update_attrs %{ - # is_enable: false + # is_enabled: false # } - # @invalid_attrs %{is_enable: nil} + # @invalid_attrs %{is_enabled: nil} # setup %{conn: conn} do # {:ok, conn: put_req_header(conn, "accept", "application/json")} @@ -33,7 +33,7 @@ defmodule CadetWeb.NotificationPreferenceControllerTest do # assert %{ # "id" => ^id, - # "is_enable" => true + # "is_enabled" => true # } = json_response(conn, 200)["data"] # end @@ -54,7 +54,7 @@ defmodule CadetWeb.NotificationPreferenceControllerTest do # assert %{ # "id" => ^id, - # "is_enable" => false + # "is_enabled" => false # } = json_response(conn, 200)["data"] # end From 554054fa2e54812f0cd08808d67491ffc7adc9a0 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 22 Feb 2023 00:51:00 +0800 Subject: [PATCH 16/80] fix: Fix test configurations not being applied `import_config` must always appear at the bottom for environment specific configurations to be applied correctly. All configurations after this line will overwrite configurations that exists in the environment specific ones. --- config/config.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.exs b/config/config.exs index cc752cc04..244fe1e2e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -72,10 +72,6 @@ config :sentry, root_source_code_path: File.cwd!(), context_lines: 5 -# Import environment specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "#{Mix.env()}.exs" - # Configure Phoenix Swagger config :cadet, :phoenix_swagger, swagger_files: %{ @@ -108,3 +104,7 @@ config :cadet, Oban, queues: [default: 10, notifications: 1] config :cadet, Cadet.Mailer, adapter: Bamboo.LocalAdapter + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{Mix.env()}.exs" From 851a309b150bb8b8f0f1349c0e735b755547701f Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:13:04 +0800 Subject: [PATCH 17/80] chore: Add tests for notifications --- .../notifications/notification_config.ex | 4 +- .../notifications/notification_preference.ex | 4 +- ...0214074219_create_notification_configs.exs | 8 +- ...140555_create_notification_preferences.exs | 8 +- .../notification_config_test.exs | 75 ++++++++++++++ .../notification_preference_test.exs | 99 +++++++++++++++++++ test/factories/factory.ex | 6 ++ .../notifcation_config_factory.ex | 20 ++++ .../notification_type_factory.ex | 20 ++++ .../notifications/time_option_factory.ex | 19 ++++ 10 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 test/cadet/notifications/notification_config_test.exs create mode 100644 test/cadet/notifications/notification_preference_test.exs create mode 100644 test/factories/notifications/notifcation_config_factory.ex create mode 100644 test/factories/notifications/notification_type_factory.ex create mode 100644 test/factories/notifications/time_option_factory.ex diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex index 82f38c8a6..b430f1a24 100644 --- a/lib/cadet/notifications/notification_config.ex +++ b/lib/cadet/notifications/notification_config.ex @@ -18,7 +18,7 @@ defmodule Cadet.Notifications.NotificationConfig do @doc false def changeset(notification_config, attrs) do notification_config - |> cast(attrs, [:is_enabled]) - |> validate_required([:is_enabled]) + |> cast(attrs, [:is_enabled, :notification_type_id, :course_id]) + |> validate_required([:is_enabled, :notification_type_id, :course_id]) end end diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index 82571fb03..2085bd62f 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -18,7 +18,7 @@ defmodule Cadet.Notifications.NotificationPreference do @doc false def changeset(notification_preference, attrs) do notification_preference - |> cast(attrs, [:is_enabled]) - |> validate_required([:is_enabled]) + |> cast(attrs, [:is_enabled, :notification_config_id, :course_reg_id]) + |> validate_required([:is_enabled, :notification_config_id, :course_reg_id]) end end diff --git a/priv/repo/migrations/20230214074219_create_notification_configs.exs b/priv/repo/migrations/20230214074219_create_notification_configs.exs index 340bf1a55..290a129ce 100644 --- a/priv/repo/migrations/20230214074219_create_notification_configs.exs +++ b/priv/repo/migrations/20230214074219_create_notification_configs.exs @@ -4,8 +4,12 @@ defmodule Cadet.Repo.Migrations.CreateNotificationConfigs do def change do create table(:notification_configs) do add(:is_enabled, :boolean, default: false, null: false) - add(:notification_type_id, references(:notification_types, on_delete: :delete_all)) - add(:course_id, references(:courses, on_delete: :delete_all)) + + add(:notification_type_id, references(:notification_types, on_delete: :delete_all), + null: false + ) + + add(:course_id, references(:courses, on_delete: :delete_all), null: false) add(:assessment_config_id, references(:assessment_configs, on_delete: :delete_all)) timestamps() diff --git a/priv/repo/migrations/20230214140555_create_notification_preferences.exs b/priv/repo/migrations/20230214140555_create_notification_preferences.exs index 155826666..77e44c31d 100644 --- a/priv/repo/migrations/20230214140555_create_notification_preferences.exs +++ b/priv/repo/migrations/20230214140555_create_notification_preferences.exs @@ -4,9 +4,13 @@ defmodule Cadet.Repo.Migrations.CreateNotificationPreferences do def change do create table(:notification_preferences) do add(:is_enabled, :boolean, default: false, null: false) - add(:notification_config_id, references(:notification_configs, on_delete: :delete_all)) + + add(:notification_config_id, references(:notification_configs, on_delete: :delete_all), + null: false + ) + add(:time_option_id, references(:time_options, on_delete: :nothing)) - add(:course_reg_id, references(:course_registrations, on_delete: :delete_all)) + add(:course_reg_id, references(:course_registrations, on_delete: :delete_all), null: false) timestamps() end diff --git a/test/cadet/notifications/notification_config_test.exs b/test/cadet/notifications/notification_config_test.exs new file mode 100644 index 000000000..93054b9a4 --- /dev/null +++ b/test/cadet/notifications/notification_config_test.exs @@ -0,0 +1,75 @@ +defmodule Cadet.Notifications.NotificationConfigTest do + alias Cadet.Notifications.NotificationConfig + + use Cadet.ChangesetCase, entity: NotificationConfig + + setup do + course1 = insert(:course, %{course_short_name: "course 1"}) + course2 = insert(:course, %{course_short_name: "course 2"}) + config1 = insert(:assessment_config, %{course: course1}) + + noti_type1 = insert(:notification_type, %{name: "Notification Type 1"}) + noti_type2 = insert(:notification_type, %{name: "Notification Type 2"}) + + {:ok, + %{ + course1: course1, + course2: course2, + config1: config1, + noti_type1: noti_type1, + noti_type2: noti_type2 + }} + end + + describe "Changesets" do + test "valid changesets", %{ + course1: course1, + course2: course2, + config1: config1, + noti_type1: noti_type1, + noti_type2: noti_type2 + } do + assert_changeset( + %{ + notification_type_id: noti_type1.id, + course_id: course1.id, + config_id: config1.id + }, + :valid + ) + + assert_changeset( + %{ + notification_type_id: noti_type2.id, + course_id: course2.id, + config_id: nil + }, + :valid + ) + end + + test "invalid changesets missing notification type" do + assert_changeset( + %{ + notification_type_id: nil, + course_id: nil, + config_id: nil + }, + :invalid + ) + end + + test "invalid changesets missing course", %{ + noti_type1: noti_type1 + } do + assert_changeset( + %{ + notification_type_id: noti_type1.id, + course_id: nil, + config_id: nil + }, + :invalid + ) + end + end +end diff --git a/test/cadet/notifications/notification_preference_test.exs b/test/cadet/notifications/notification_preference_test.exs new file mode 100644 index 000000000..4a71baefe --- /dev/null +++ b/test/cadet/notifications/notification_preference_test.exs @@ -0,0 +1,99 @@ +defmodule Cadet.Notifications.NotificationPreferenceTest do + alias Cadet.Notifications.NotificationPreference + + use Cadet.ChangesetCase, entity: NotificationPreference + + setup do + course1 = insert(:course, %{course_short_name: "course 1"}) + config1 = insert(:assessment_config, %{course: course1}) + + student_user = insert(:user) + avenger_user = insert(:user) + avenger = insert(:course_registration, %{user: avenger_user, course: course1, role: :staff}) + student = insert(:course_registration, %{user: student_user, course: course1, role: :student}) + + noti_type1 = insert(:notification_type, %{name: "Notification Type 1"}) + + noti_config1 = + insert(:notification_config, %{ + notification_type: noti_type1, + course: course1, + assessment_config: config1 + }) + + time_option1 = + insert(:time_option, %{ + notification_config: noti_config1 + }) + + {:ok, + %{ + course1: course1, + config1: config1, + student: student, + avenger: avenger, + noti_type1: noti_type1, + noti_config1: noti_config1, + time_option1: time_option1 + }} + end + + describe "Changesets" do + test "valid changesets", %{ + student: student, + avenger: avenger, + noti_config1: noti_config1, + time_option1: time_option1 + } do + assert_changeset( + %{ + is_enabled: false, + notification_config_id: noti_config1.id, + time_option_id: time_option1.id, + course_reg_id: student.id + }, + :valid + ) + + assert_changeset( + %{ + is_enabled: false, + notification_config_id: noti_config1.id, + time_option_id: time_option1.id, + course_reg_id: avenger.id + }, + :valid + ) + end + + test "invalid changesets missing notification config", %{ + avenger: avenger, + time_option1: time_option1 + } do + assert_changeset( + %{ + is_enabled: false, + notification_config_id: nil, + time_option_id: time_option1.id, + course_reg_id: avenger.id + }, + :invalid + ) + end + + test "invalid changesets missing course registration", %{ + noti_config1: noti_config1, + time_option1: time_option1 + } do + assert_changeset( + %{ + is_enabled: false, + notification_config_id: noti_config1.id, + time_option_id: time_option1.id, + course_reg_id: nil + }, + :invalid + ) + end + end +end diff --git a/test/factories/factory.ex b/test/factories/factory.ex index 6d8fe077c..fb3a28cd0 100644 --- a/test/factories/factory.ex +++ b/test/factories/factory.ex @@ -29,6 +29,12 @@ defmodule Cadet.Factory do SourcecastFactory } + use Cadet.Notifications.{ + NotificationTypeFactory, + NotificationConfigFactory, + TimeOptionFactory + } + use Cadet.Devices.DeviceFactory def upload_factory do diff --git a/test/factories/notifications/notifcation_config_factory.ex b/test/factories/notifications/notifcation_config_factory.ex new file mode 100644 index 000000000..78942100b --- /dev/null +++ b/test/factories/notifications/notifcation_config_factory.ex @@ -0,0 +1,20 @@ +defmodule Cadet.Notifications.NotificationConfigFactory do + @moduledoc """ + Factory for the NotificationConfig entity + """ + + defmacro __using__(_opts) do + quote do + alias Cadet.Notifications.NotificationConfig + + def notification_config_factory do + %NotificationConfig{ + is_enabled: false, + notification_type: build(:notification_type), + course: build(:course), + assessment_config: build(:assessment_config), + } + end + end + end +end diff --git a/test/factories/notifications/notification_type_factory.ex b/test/factories/notifications/notification_type_factory.ex new file mode 100644 index 000000000..5c7995564 --- /dev/null +++ b/test/factories/notifications/notification_type_factory.ex @@ -0,0 +1,20 @@ +defmodule Cadet.Notifications.NotificationTypeFactory do + @moduledoc """ + Factory for the NotificationType entity + """ + + defmacro __using__(_opts) do + quote do + alias Cadet.Notifications.NotificationType + + def notification_type_factory do + %NotificationType{ + is_autopopulated: false, + is_enabled: false, + name: "Generic Notificaation Type", + template_file_name: "generic_template_name" + } + end + end + end +end diff --git a/test/factories/notifications/time_option_factory.ex b/test/factories/notifications/time_option_factory.ex new file mode 100644 index 000000000..b659bc3af --- /dev/null +++ b/test/factories/notifications/time_option_factory.ex @@ -0,0 +1,19 @@ +defmodule Cadet.Notifications.TimeOptionFactory do + @moduledoc """ + Factory for the TimeOption entity + """ + + defmacro __using__(_opts) do + quote do + alias Cadet.Notifications.TimeOption + + def time_option_factory do + %TimeOption{ + is_default: false, + minutes: 0, + notification_config: build(:notification_config), + } + end + end + end +end From fa4ea44eea84b9b9c7b6f5881650c4ca86b4a8c7 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:13:18 +0800 Subject: [PATCH 18/80] chore: Add test for avenger backlog email --- test/cadet/email_test.exs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/cadet/email_test.exs diff --git a/test/cadet/email_test.exs b/test/cadet/email_test.exs new file mode 100644 index 000000000..7e925a383 --- /dev/null +++ b/test/cadet/email_test.exs @@ -0,0 +1,22 @@ +defmodule Cadet.EmailTest do + use ExUnit.Case + use Bamboo.Test + alias Cadet.Email + + use Cadet.ChangesetCase, entity: Email + + test "avenger backlog email" do + avenger_user = insert(:user, %{email: "test@gmail.com"}) + avenger = insert(:course_registration, %{user: avenger_user, role: :staff}) + + ungraded_submissions = + Jason.decode!( + elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger, true, true), 1) + ) + + Cadet.Email.avenger_backlog_email("avenger_backlog", avenger_user, ungraded_submissions) + + avenger_email = avenger_user.email + assert_delivered_email_matches(%{to: [{_, ^avenger_email}]}) + end +end From 6602170eb4521ecec065891658eef4bdab80eb42 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:37:56 +0800 Subject: [PATCH 19/80] chore: Resolve style violations --- .../notifications/notifications_test.exs | 72 +++++++++++++------ .../notification_config_controller_test.exs | 29 ++++++-- ...otification_preference_controller_test.exs | 49 +++++++++---- .../notification_type_controller_test.exs | 60 +++++++++++----- .../sent_notification_controller_test.exs | 54 +++++++++----- .../time_option_controller_test.exs | 17 ++++- 6 files changed, 204 insertions(+), 77 deletions(-) diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index bb6c53abc..b715e66be 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -21,9 +21,15 @@ defmodule Cadet.NotificationsTest do # end # test "create_notification_type/1 with valid data creates a notification_type" do - # valid_attrs = %{is_autopopulated: true, is_enabled: true, name: "some name", template_file_name: "some template_file_name"} - - # assert {:ok, %NotificationType{} = notification_type} = Notifications.create_notification_type(valid_attrs) + # valid_attrs = %{ + # is_autopopulated: true, + # is_enabled: true, + # name: "some name", + # template_file_name: "some template_file_name" + # } + + # assert {:ok, %NotificationType{} = notification_type} = + # Notifications.create_notification_type(valid_attrs) # assert notification_type.is_autopopulated == true # assert notification_type.is_enabled == true # assert notification_type.name == "some name" @@ -36,9 +42,15 @@ defmodule Cadet.NotificationsTest do # test "update_notification_type/2 with valid data updates the notification_type" do # notification_type = notification_type_fixture() - # update_attrs = %{is_autopopulated: false, is_enabled: false, name: "some updated name", template_file_name: "some updated template_file_name"} - - # assert {:ok, %NotificationType{} = notification_type} = Notifications.update_notification_type(notification_type, update_attrs) + # update_attrs = %{ + # is_autopopulated: false, + # is_enabled: false, + # name: "some updated name", + # template_file_name: "some updated template_file_name" + # } + + # assert {:ok, %NotificationType{} = notification_type} = + # Notifications.update_notification_type(notification_type, update_attrs) # assert notification_type.is_autopopulated == false # assert notification_type.is_enabled == false # assert notification_type.name == "some updated name" @@ -47,7 +59,8 @@ defmodule Cadet.NotificationsTest do # test "update_notification_type/2 with invalid data returns error changeset" do # notification_type = notification_type_fixture() - # assert {:error, %Ecto.Changeset{}} = Notifications.update_notification_type(notification_type, @invalid_attrs) + # assert {:error, %Ecto.Changeset{}} = + # Notifications.update_notification_type(notification_type, @invalid_attrs) # assert notification_type == Notifications.get_notification_type!(notification_type.id) # end @@ -83,7 +96,8 @@ defmodule Cadet.NotificationsTest do # test "create_notification_config/1 with valid data creates a notification_config" do # valid_attrs = %{is_enabled: true} - # assert {:ok, %NotificationConfig{} = notification_config} = Notifications.create_notification_config(valid_attrs) + # assert {:ok, %NotificationConfig{} = notification_config} = + # Notifications.create_notification_config(valid_attrs) # assert notification_config.is_enabled == true # end @@ -95,19 +109,22 @@ defmodule Cadet.NotificationsTest do # notification_config = notification_config_fixture() # update_attrs = %{is_enabled: false} - # assert {:ok, %NotificationConfig{} = notification_config} = Notifications.update_notification_config(notification_config, update_attrs) + # assert {:ok, %NotificationConfig{} = notification_config} = + # Notifications.update_notification_config(notification_config, update_attrs) # assert notification_config.is_enabled == false # end # test "update_notification_config/2 with invalid data returns error changeset" do # notification_config = notification_config_fixture() - # assert {:error, %Ecto.Changeset{}} = Notifications.update_notification_config(notification_config, @invalid_attrs) + # assert {:error, %Ecto.Changeset{}} = + # Notifications.update_notification_config(notification_config, @invalid_attrs) # assert notification_config == Notifications.get_notification_config!(notification_config.id) # end # test "delete_notification_config/1 deletes the notification_config" do # notification_config = notification_config_fixture() - # assert {:ok, %NotificationConfig{}} = Notifications.delete_notification_config(notification_config) + # assert {:ok, %NotificationConfig{}} = + # Notifications.delete_notification_config(notification_config) # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_config!(notification_config.id) end # end @@ -150,14 +167,16 @@ defmodule Cadet.NotificationsTest do # time_option = time_option_fixture() # update_attrs = %{is_default: false, minutes: 43} - # assert {:ok, %TimeOption{} = time_option} = Notifications.update_time_option(time_option, update_attrs) + # assert {:ok, %TimeOption{} = time_option} = + # Notifications.update_time_option(time_option, update_attrs) # assert time_option.is_default == false # assert time_option.minutes == 43 # end # test "update_time_option/2 with invalid data returns error changeset" do # time_option = time_option_fixture() - # assert {:error, %Ecto.Changeset{}} = Notifications.update_time_option(time_option, @invalid_attrs) + # assert {:error, %Ecto.Changeset{}} = + # Notifications.update_time_option(time_option, @invalid_attrs) # assert time_option == Notifications.get_time_option!(time_option.id) # end @@ -193,37 +212,43 @@ defmodule Cadet.NotificationsTest do # test "create_notification_preference/1 with valid data creates a notification_preference" do # valid_attrs = %{is_enabled: true} - # assert {:ok, %NotificationPreference{} = notification_preference} = Notifications.create_notification_preference(valid_attrs) + # assert {:ok, %NotificationPreference{} = notification_preference} = + # Notifications.create_notification_preference(valid_attrs) # assert notification_preference.is_enabled == true # end # test "create_notification_preference/1 with invalid data returns error changeset" do - # assert {:error, %Ecto.Changeset{}} = Notifications.create_notification_preference(@invalid_attrs) + # assert {:error, %Ecto.Changeset{}} = + # Notifications.create_notification_preference(@invalid_attrs) # end # test "update_notification_preference/2 with valid data updates the notification_preference" do # notification_preference = notification_preference_fixture() # update_attrs = %{is_enabled: false} - # assert {:ok, %NotificationPreference{} = notification_preference} = Notifications.update_notification_preference(notification_preference, update_attrs) + # assert {:ok, %NotificationPreference{} = notification_preference} = + # Notifications.update_notification_preference(notification_preference, update_attrs) # assert notification_preference.is_enabled == false # end # test "update_notification_preference/2 with invalid data returns error changeset" do # notification_preference = notification_preference_fixture() - # assert {:error, %Ecto.Changeset{}} = Notifications.update_notification_preference(notification_preference, @invalid_attrs) + # assert {:error, %Ecto.Changeset{}} = + # Notifications.update_notification_preference(notification_preference, @invalid_attrs) # assert notification_preference == Notifications.get_notification_preference!(notification_preference.id) # end # test "delete_notification_preference/1 deletes the notification_preference" do # notification_preference = notification_preference_fixture() - # assert {:ok, %NotificationPreference{}} = Notifications.delete_notification_preference(notification_preference) + # assert {:ok, %NotificationPreference{}} = + # Notifications.delete_notification_preference(notification_preference) # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_preference!(notification_preference.id) end # end # test "change_notification_preference/1 returns a notification_preference changeset" do # notification_preference = notification_preference_fixture() - # assert %Ecto.Changeset{} = Notifications.change_notification_preference(notification_preference) + # assert %Ecto.Changeset{} = + # Notifications.change_notification_preference(notification_preference) # end end @@ -247,7 +272,8 @@ defmodule Cadet.NotificationsTest do # test "create_sent_notification/1 with valid data creates a sent_notification" do # valid_attrs = %{content: "some content"} - # assert {:ok, %SentNotification{} = sent_notification} = Notifications.create_sent_notification(valid_attrs) + # assert {:ok, %SentNotification{} = sent_notification} = + # Notifications.create_sent_notification(valid_attrs) # assert sent_notification.content == "some content" # end @@ -259,13 +285,15 @@ defmodule Cadet.NotificationsTest do # sent_notification = sent_notification_fixture() # update_attrs = %{content: "some updated content"} - # assert {:ok, %SentNotification{} = sent_notification} = Notifications.update_sent_notification(sent_notification, update_attrs) + # assert {:ok, %SentNotification{} = sent_notification} = + # Notifications.update_sent_notification(sent_notification, update_attrs) # assert sent_notification.content == "some updated content" # end # test "update_sent_notification/2 with invalid data returns error changeset" do # sent_notification = sent_notification_fixture() - # assert {:error, %Ecto.Changeset{}} = Notifications.update_sent_notification(sent_notification, @invalid_attrs) + # assert {:error, %Ecto.Changeset{}} = + # Notifications.update_sent_notification(sent_notification, @invalid_attrs) # assert sent_notification == Notifications.get_sent_notification!(sent_notification.id) # end diff --git a/test/cadet_web/controllers/notification_config_controller_test.exs b/test/cadet_web/controllers/notification_config_controller_test.exs index f10eee15d..22ae1559b 100644 --- a/test/cadet_web/controllers/notification_config_controller_test.exs +++ b/test/cadet_web/controllers/notification_config_controller_test.exs @@ -26,7 +26,11 @@ defmodule CadetWeb.NotificationConfigControllerTest do # describe "create notification_config" do # test "renders notification_config when data is valid", %{conn: conn} do - # conn = post(conn, Routes.notification_config_path(conn, :create), notification_config: @create_attrs) + # conn = post( + # conn, + # Routes.notification_config_path(conn, :create), + # notification_config: @create_attrs + # ) # assert %{"id" => id} = json_response(conn, 201)["data"] # conn = get(conn, Routes.notification_config_path(conn, :show, id)) @@ -38,7 +42,11 @@ defmodule CadetWeb.NotificationConfigControllerTest do # end # test "renders errors when data is invalid", %{conn: conn} do - # conn = post(conn, Routes.notification_config_path(conn, :create), notification_config: @invalid_attrs) + # conn = post( + # conn, + # Routes.notification_config_path(conn, :create), + # notification_config: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end @@ -46,8 +54,15 @@ defmodule CadetWeb.NotificationConfigControllerTest do # describe "update notification_config" do # setup [:create_notification_config] - # test "renders notification_config when data is valid", %{conn: conn, notification_config: %NotificationConfig{id: id} = notification_config} do - # conn = put(conn, Routes.notification_config_path(conn, :update, notification_config), notification_config: @update_attrs) + # test "renders notification_config when data is valid", %{ + # conn: conn, + # notification_config: %NotificationConfig{id: id} = notification_config + # } do + # conn = put( + # conn, + # Routes.notification_config_path(conn, :update, notification_config), + # notification_config: @update_attrs + # ) # assert %{"id" => ^id} = json_response(conn, 200)["data"] # conn = get(conn, Routes.notification_config_path(conn, :show, id)) @@ -59,7 +74,11 @@ defmodule CadetWeb.NotificationConfigControllerTest do # end # test "renders errors when data is invalid", %{conn: conn, notification_config: notification_config} do - # conn = put(conn, Routes.notification_config_path(conn, :update, notification_config), notification_config: @invalid_attrs) + # conn = put( + # conn, + # Routes.notification_config_path(conn, :update, notification_config), + # notification_config: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end diff --git a/test/cadet_web/controllers/notification_preference_controller_test.exs b/test/cadet_web/controllers/notification_preference_controller_test.exs index ec283437d..6610512a3 100644 --- a/test/cadet_web/controllers/notification_preference_controller_test.exs +++ b/test/cadet_web/controllers/notification_preference_controller_test.exs @@ -26,7 +26,8 @@ defmodule CadetWeb.NotificationPreferenceControllerTest do # describe "create notification_preference" do # test "renders notification_preference when data is valid", %{conn: conn} do - # conn = post(conn, Routes.notification_preference_path(conn, :create), notification_preference: @create_attrs) + # conn = post(conn, Routes.notification_preference_path(conn, :create), + # notification_preference: @create_attrs) # assert %{"id" => id} = json_response(conn, 201)["data"] # conn = get(conn, Routes.notification_preference_path(conn, :show, id)) @@ -38,7 +39,11 @@ defmodule CadetWeb.NotificationPreferenceControllerTest do # end # test "renders errors when data is invalid", %{conn: conn} do - # conn = post(conn, Routes.notification_preference_path(conn, :create), notification_preference: @invalid_attrs) + # conn = post( + # conn, + # Routes.notification_preference_path(conn, :create), + # notification_preference: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end @@ -46,20 +51,34 @@ defmodule CadetWeb.NotificationPreferenceControllerTest do # describe "update notification_preference" do # setup [:create_notification_preference] - # test "renders notification_preference when data is valid", %{conn: conn, notification_preference: %NotificationPreference{id: id} = notification_preference} do - # conn = put(conn, Routes.notification_preference_path(conn, :update, notification_preference), notification_preference: @update_attrs) + # test "renders notification_preference when data is valid", %{ + # conn: conn, + # notification_preference: %NotificationPreference{id: id} = notification_preference + # } do + # conn = put( + # conn, + # Routes.notification_preference_path(conn, :update, notification_preference), + # notification_preference: @update_attrs + # ) # assert %{"id" => ^id} = json_response(conn, 200)["data"] # conn = get(conn, Routes.notification_preference_path(conn, :show, id)) # assert %{ - # "id" => ^id, - # "is_enabled" => false - # } = json_response(conn, 200)["data"] - # end + # "id" => ^id, + # "is_enabled" => false + # } = json_response(conn, 200)["data"] + # end - # test "renders errors when data is invalid", %{conn: conn, notification_preference: notification_preference} do - # conn = put(conn, Routes.notification_preference_path(conn, :update, notification_preference), notification_preference: @invalid_attrs) + # test "renders errors when data is invalid", %{ + # conn: conn, + # notification_preference: notification_preference + # } do + # conn = put( + # conn, + # Routes.notification_preference_path(conn, :update, notification_preference), + # notification_preference: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end @@ -67,8 +86,14 @@ defmodule CadetWeb.NotificationPreferenceControllerTest do # describe "delete notification_preference" do # setup [:create_notification_preference] - # test "deletes chosen notification_preference", %{conn: conn, notification_preference: notification_preference} do - # conn = delete(conn, Routes.notification_preference_path(conn, :delete, notification_preference)) + # test "deletes chosen notification_preference", %{ + # conn: conn, + # notification_preference: notification_preference + # } do + # conn = delete( + # conn, + # Routes.notification_preference_path(conn, :delete, notification_preference) + # ) # assert response(conn, 204) # assert_error_sent 404, fn -> diff --git a/test/cadet_web/controllers/notification_type_controller_test.exs b/test/cadet_web/controllers/notification_type_controller_test.exs index b7a47f95a..d7ea2d90f 100644 --- a/test/cadet_web/controllers/notification_type_controller_test.exs +++ b/test/cadet_web/controllers/notification_type_controller_test.exs @@ -32,7 +32,11 @@ defmodule CadetWeb.NotificationTypeControllerTest do # describe "create notification_type" do # test "renders notification_type when data is valid", %{conn: conn} do - # conn = post(conn, Routes.notification_type_path(conn, :create), notification_type: @create_attrs) + # conn = post( + # conn, + # Routes.notification_type_path(conn, :create), + # notification_type: @create_attrs + # ) # assert %{"id" => id} = json_response(conn, 201)["data"] # conn = get(conn, Routes.notification_type_path(conn, :show, id)) @@ -47,7 +51,11 @@ defmodule CadetWeb.NotificationTypeControllerTest do # end # test "renders errors when data is invalid", %{conn: conn} do - # conn = post(conn, Routes.notification_type_path(conn, :create), notification_type: @invalid_attrs) + # conn = post( + # conn, + # Routes.notification_type_path(conn, :create), + # notification_type: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end @@ -55,23 +63,37 @@ defmodule CadetWeb.NotificationTypeControllerTest do # describe "update notification_type" do # setup [:create_notification_type] - # test "renders notification_type when data is valid", %{conn: conn, notification_type: %NotificationType{id: id} = notification_type} do - # conn = put(conn, Routes.notification_type_path(conn, :update, notification_type), notification_type: @update_attrs) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.notification_type_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_autopopulated" => false, - # "is_enabled" => false, - # "name" => "some updated name", - # "template_file_name" => "some updated template_file_name" - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn, notification_type: notification_type} do - # conn = put(conn, Routes.notification_type_path(conn, :update, notification_type), notification_type: @invalid_attrs) + # test "renders notification_type when data is valid", %{ + # conn: conn, + # notification_type: %NotificationType{id: id} = notification_type + # } do + # conn = put( + # conn, + # Routes.notification_type_path(conn, :update, notification_type), + # notification_type: @update_attrs + # ) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.notification_type_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_autopopulated" => false, + # "is_enabled" => false, + # "name" => "some updated name", + # "template_file_name" => "some updated template_file_name" + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{ + # conn: conn, + # notification_type: notification_type + # } do + # conn = put( + # conn, + # Routes.notification_type_path(conn, :update, notification_type), + # notification_type: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end diff --git a/test/cadet_web/controllers/sent_notification_controller_test.exs b/test/cadet_web/controllers/sent_notification_controller_test.exs index fd5bcaab6..6e3e870b8 100644 --- a/test/cadet_web/controllers/sent_notification_controller_test.exs +++ b/test/cadet_web/controllers/sent_notification_controller_test.exs @@ -26,7 +26,11 @@ defmodule CadetWeb.SentNotificationControllerTest do # describe "create sent_notification" do # test "renders sent_notification when data is valid", %{conn: conn} do - # conn = post(conn, Routes.sent_notification_path(conn, :create), sent_notification: @create_attrs) + # conn = post( + # conn, + # Routes.sent_notification_path(conn, :create), + # sent_notification: @create_attrs + # ) # assert %{"id" => id} = json_response(conn, 201)["data"] # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) @@ -38,7 +42,11 @@ defmodule CadetWeb.SentNotificationControllerTest do # end # test "renders errors when data is invalid", %{conn: conn} do - # conn = post(conn, Routes.sent_notification_path(conn, :create), sent_notification: @invalid_attrs) + # conn = post( + # conn, + # Routes.sent_notification_path(conn, :create), + # sent_notification: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end @@ -46,20 +54,34 @@ defmodule CadetWeb.SentNotificationControllerTest do # describe "update sent_notification" do # setup [:create_sent_notification] - # test "renders sent_notification when data is valid", %{conn: conn, sent_notification: %SentNotification{id: id} = sent_notification} do - # conn = put(conn, Routes.sent_notification_path(conn, :update, sent_notification), sent_notification: @update_attrs) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "content" => "some updated content" - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn, sent_notification: sent_notification} do - # conn = put(conn, Routes.sent_notification_path(conn, :update, sent_notification), sent_notification: @invalid_attrs) + # test "renders sent_notification when data is valid", %{ + # conn: conn, + # sent_notification: %SentNotification{id: id} = sent_notification + # } do + # conn = put( + # conn, + # Routes.sent_notification_path(conn, :update, sent_notification), + # sent_notification: @update_attrs + # ) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "content" => "some updated content" + # } = json_response(conn, 200)["data"] + # end + + # test "renders errors when data is invalid", %{ + # conn: conn, + # sent_notification: sent_notification + # } do + # conn = put( + # conn, + # Routes.sent_notification_path(conn, :update, sent_notification), + # sent_notification: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end diff --git a/test/cadet_web/controllers/time_option_controller_test.exs b/test/cadet_web/controllers/time_option_controller_test.exs index f43624a3b..bcf46d9f9 100644 --- a/test/cadet_web/controllers/time_option_controller_test.exs +++ b/test/cadet_web/controllers/time_option_controller_test.exs @@ -49,8 +49,15 @@ defmodule CadetWeb.TimeOptionControllerTest do # describe "update time_option" do # setup [:create_time_option] - # test "renders time_option when data is valid", %{conn: conn, time_option: %TimeOption{id: id} = time_option} do - # conn = put(conn, Routes.time_option_path(conn, :update, time_option), time_option: @update_attrs) + # test "renders time_option when data is valid", %{ + # conn: conn, + # time_option: %TimeOption{id: id} = time_option + # } do + # conn = put( + # conn, + # Routes.time_option_path(conn, :update, time_option), + # time_option: @update_attrs + # ) # assert %{"id" => ^id} = json_response(conn, 200)["data"] # conn = get(conn, Routes.time_option_path(conn, :show, id)) @@ -63,7 +70,11 @@ defmodule CadetWeb.TimeOptionControllerTest do # end # test "renders errors when data is invalid", %{conn: conn, time_option: time_option} do - # conn = put(conn, Routes.time_option_path(conn, :update, time_option), time_option: @invalid_attrs) + # conn = put( + # conn, + # Routes.time_option_path(conn, :update, time_option), + # time_option: @invalid_attrs + # ) # assert json_response(conn, 422)["errors"] != %{} # end # end From 669ac750d065f877ea274d71d089cbc3a576d428 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 27 Feb 2023 17:31:03 +0800 Subject: [PATCH 20/80] chore: Resolve style violations --- lib/cadet/assessments/assessments.ex | 6 +- lib/cadet/email.ex | 23 +- lib/cadet/jobs/scheduler.ex | 3 + lib/cadet/mailer.ex | 3 + lib/cadet/notifications.ex | 252 +++++++++--------- .../notifications/notification_config.ex | 3 + .../notifications/notification_preference.ex | 3 + lib/cadet/notifications/notification_type.ex | 5 + lib/cadet/notifications/sent_notification.ex | 3 + lib/cadet/notifications/time_option.ex | 3 + lib/cadet/workers/NotificationWorker.ex | 37 +-- .../sent_notification_controller_test.exs | 5 +- 12 files changed, 187 insertions(+), 159 deletions(-) diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index 4cc3de8d1..0dfe7ebd1 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -1163,9 +1163,9 @@ defmodule Cadet.Assessments do "where s.student_id in (select cr.id from course_registrations cr inner join groups g on cr.group_id = g.id where g.leader_id = $2) or s.student_id = $2" ungraded_where = - if not ungraded_only, - do: "", - else: "where s.\"gradedCount\" < assts.\"questionCount\"" + if ungraded_only, + do: "where s.\"gradedCount\" < assts.\"questionCount\"", + else: "" params = if show_all, do: [course_id], else: [course_id, grader.id] diff --git a/lib/cadet/email.ex b/lib/cadet/email.ex index dc5724557..014cc37bf 100644 --- a/lib/cadet/email.ex +++ b/lib/cadet/email.ex @@ -4,22 +4,21 @@ defmodule Cadet.Email do alias Cadet.Mailer def avenger_backlog_email(template_file_name, avenger, ungraded_submissions) do - cond do - is_nil(avenger.email) -> - nil + if is_nil(avenger.email) do + nil - true -> - base_email() - |> to(avenger.email) - |> assign(:avenger_name, avenger.name) - |> assign(:submissions, ungraded_submissions) - |> subject("Backlog for #{avenger.name}") - |> render("#{template_file_name}.html") - |> Mailer.deliver_now() + else + base_email() + |> to(avenger.email) + |> assign(:avenger_name, avenger.name) + |> assign(:submissions, ungraded_submissions) + |> subject("Backlog for #{avenger.name}") + |> render("#{template_file_name}.html") + |> Mailer.deliver_now() end end - defp base_email() do + defp base_email do new_email() |> from("noreply@sourceacademy.org") |> put_html_layout({CadetWeb.LayoutView, "email.html"}) diff --git a/lib/cadet/jobs/scheduler.ex b/lib/cadet/jobs/scheduler.ex index 06a10ae68..dd09922dc 100644 --- a/lib/cadet/jobs/scheduler.ex +++ b/lib/cadet/jobs/scheduler.ex @@ -1,5 +1,8 @@ # credo:disable-for-this-file Credo.Check.Readability.ModuleDoc # @moduledoc is actually generated by a macro inside Quantum defmodule Cadet.Jobs.Scheduler do + @moduledoc """ + Quantum is used for scheduling jobs with cron jobs. + """ use Quantum, otp_app: :cadet end diff --git a/lib/cadet/mailer.ex b/lib/cadet/mailer.ex index aded6c32d..f88cfd706 100644 --- a/lib/cadet/mailer.ex +++ b/lib/cadet/mailer.ex @@ -1,3 +1,6 @@ defmodule Cadet.Mailer do + @moduledoc """ + Mailer used to sent notification emails. + """ use Bamboo.Mailer, otp_app: :cadet end diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 5bb252af9..bf8714fbe 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -11,12 +11,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of notification_types. - + ## Examples - + iex> list_notification_types() [%NotificationType{}, ...] - + """ def list_notification_types do Repo.all(NotificationType) @@ -24,31 +24,31 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_type. - + Raises `Ecto.NoResultsError` if the Notification type does not exist. - + ## Examples - + iex> get_notification_type!(123) %NotificationType{} - + iex> get_notification_type!(456) ** (Ecto.NoResultsError) - + """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) @doc """ Creates a notification_type. - + ## Examples - + iex> create_notification_type(%{field: value}) {:ok, %NotificationType{}} - + iex> create_notification_type(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_type(attrs \\ %{}) do %NotificationType{} @@ -58,17 +58,17 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_type. - + ## Examples - + iex> update_notification_type(notification_type, %{field: new_value}) {:ok, %NotificationType{}} - + iex> update_notification_type(notification_type, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ - def update_notification_type(%NotificationType{} = notification_type, attrs) do + def update_notification_type(notification_type = %NotificationType{}, attrs) do notification_type |> NotificationType.changeset(attrs) |> Repo.update() @@ -76,41 +76,41 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_type. - + ## Examples - + iex> delete_notification_type(notification_type) {:ok, %NotificationType{}} - + iex> delete_notification_type(notification_type) {:error, %Ecto.Changeset{}} - + """ - def delete_notification_type(%NotificationType{} = notification_type) do + def delete_notification_type(notification_type = %NotificationType{}) do Repo.delete(notification_type) end @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_type changes. - + ## Examples - + iex> change_notification_type(notification_type) %Ecto.Changeset{data: %NotificationType{}} - + """ - def change_notification_type(%NotificationType{} = notification_type, attrs \\ %{}) do + def change_notification_type(notification_type = %NotificationType{}, attrs \\ %{}) do NotificationType.changeset(notification_type, attrs) end @doc """ Returns the list of notification_configs. - + ## Examples - + iex> list_notification_configs() [%NotificationConfig{}, ...] - + """ def list_notification_configs do Repo.all(NotificationConfig) @@ -118,17 +118,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_config. - + Raises `Ecto.NoResultsError` if the Notification config does not exist. - + ## Examples - + iex> get_notification_config!(123) %NotificationConfig{} - + iex> get_notification_config!(456) ** (Ecto.NoResultsError) - + """ def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) @@ -152,15 +152,15 @@ defmodule Cadet.Notifications do @doc """ Creates a notification_config. - + ## Examples - + iex> create_notification_config(%{field: value}) {:ok, %NotificationConfig{}} - + iex> create_notification_config(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_config(attrs \\ %{}) do %NotificationConfig{} @@ -170,17 +170,17 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_config. - + ## Examples - + iex> update_notification_config(notification_config, %{field: new_value}) {:ok, %NotificationConfig{}} - + iex> update_notification_config(notification_config, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ - def update_notification_config(%NotificationConfig{} = notification_config, attrs) do + def update_notification_config(notification_config = %NotificationConfig{}, attrs) do notification_config |> NotificationConfig.changeset(attrs) |> Repo.update() @@ -188,30 +188,30 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_config. - + ## Examples - + iex> delete_notification_config(notification_config) {:ok, %NotificationConfig{}} - + iex> delete_notification_config(notification_config) {:error, %Ecto.Changeset{}} - + """ - def delete_notification_config(%NotificationConfig{} = notification_config) do + def delete_notification_config(notification_config = %NotificationConfig{}) do Repo.delete(notification_config) end @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_config changes. - + ## Examples - + iex> change_notification_config(notification_config) %Ecto.Changeset{data: %NotificationConfig{}} - + """ - def change_notification_config(%NotificationConfig{} = notification_config, attrs \\ %{}) do + def change_notification_config(notification_config = %NotificationConfig{}, attrs \\ %{}) do NotificationConfig.changeset(notification_config, attrs) end @@ -219,12 +219,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of time_options. - + ## Examples - + iex> list_time_options() [%TimeOption{}, ...] - + """ def list_time_options do Repo.all(TimeOption) @@ -232,17 +232,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single time_option. - + Raises `Ecto.NoResultsError` if the Time option does not exist. - + ## Examples - + iex> get_time_option!(123) %TimeOption{} - + iex> get_time_option!(456) ** (Ecto.NoResultsError) - + """ def get_time_option!(id), do: Repo.get!(TimeOption, id) @@ -278,15 +278,15 @@ defmodule Cadet.Notifications do @doc """ Creates a time_option. - + ## Examples - + iex> create_time_option(%{field: value}) {:ok, %TimeOption{}} - + iex> create_time_option(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_time_option(attrs \\ %{}) do %TimeOption{} @@ -296,15 +296,15 @@ defmodule Cadet.Notifications do @doc """ Updates a time_option. - + ## Examples - + iex> update_time_option(time_option, %{field: new_value}) {:ok, %TimeOption{}} - + iex> update_time_option(time_option, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_time_option(%TimeOption{} = time_option, attrs) do time_option @@ -314,15 +314,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a time_option. - + ## Examples - + iex> delete_time_option(time_option) {:ok, %TimeOption{}} - + iex> delete_time_option(time_option) {:error, %Ecto.Changeset{}} - + """ def delete_time_option(%TimeOption{} = time_option) do Repo.delete(time_option) @@ -330,12 +330,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking time_option changes. - + ## Examples - + iex> change_time_option(time_option) %Ecto.Changeset{data: %TimeOption{}} - + """ def change_time_option(%TimeOption{} = time_option, attrs \\ %{}) do TimeOption.changeset(time_option, attrs) @@ -345,12 +345,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of notification_preferences. - + ## Examples - + iex> list_notification_preferences() [%NotificationPreference{}, ...] - + """ def list_notification_preferences do Repo.all(NotificationPreference) @@ -358,17 +358,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_preference. - + Raises `Ecto.NoResultsError` if the Notification preference does not exist. - + ## Examples - + iex> get_notification_preference!(123) %NotificationPreference{} - + iex> get_notification_preference!(456) ** (Ecto.NoResultsError) - + """ def get_notification_preference!(id), do: Repo.get!(NotificationPreference, id) @@ -388,15 +388,15 @@ defmodule Cadet.Notifications do @doc """ Creates a notification_preference. - + ## Examples - + iex> create_notification_preference(%{field: value}) {:ok, %NotificationPreference{}} - + iex> create_notification_preference(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_preference(attrs \\ %{}) do %NotificationPreference{} @@ -406,17 +406,17 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_preference. - + ## Examples - + iex> update_notification_preference(notification_preference, %{field: new_value}) {:ok, %NotificationPreference{}} - + iex> update_notification_preference(notification_preference, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ - def update_notification_preference(%NotificationPreference{} = notification_preference, attrs) do + def update_notification_preference(notification_preference = %NotificationPreference{}, attrs) do notification_preference |> NotificationPreference.changeset(attrs) |> Repo.update() @@ -424,28 +424,28 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_preference. - + ## Examples - + iex> delete_notification_preference(notification_preference) {:ok, %NotificationPreference{}} - + iex> delete_notification_preference(notification_preference) {:error, %Ecto.Changeset{}} - + """ - def delete_notification_preference(%NotificationPreference{} = notification_preference) do + def delete_notification_preference(notification_preference = %NotificationPreference{}) do Repo.delete(notification_preference) end @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_preference changes. - + ## Examples - + iex> change_notification_preference(notification_preference) %Ecto.Changeset{data: %NotificationPreference{}} - + """ def change_notification_preference( %NotificationPreference{} = notification_preference, @@ -458,12 +458,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of sent_notifications. - + ## Examples - + iex> list_sent_notifications() [%SentNotification{}, ...] - + """ def list_sent_notifications do Repo.all(SentNotification) @@ -471,31 +471,31 @@ defmodule Cadet.Notifications do @doc """ Gets a single sent_notification. - + Raises `Ecto.NoResultsError` if the Sent notification does not exist. - + ## Examples - + iex> get_sent_notification!(123) %SentNotification{} - + iex> get_sent_notification!(456) ** (Ecto.NoResultsError) - + """ def get_sent_notification!(id), do: Repo.get!(SentNotification, id) @doc """ Creates a sent_notification. - + ## Examples - + iex> create_sent_notification(%{field: value}) {:ok, %SentNotification{}} - + iex> create_sent_notification(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_sent_notification(attrs \\ %{}) do %SentNotification{} @@ -505,17 +505,17 @@ defmodule Cadet.Notifications do @doc """ Updates a sent_notification. - + ## Examples - + iex> update_sent_notification(sent_notification, %{field: new_value}) {:ok, %SentNotification{}} - + iex> update_sent_notification(sent_notification, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ - def update_sent_notification(%SentNotification{} = sent_notification, attrs) do + def update_sent_notification(sent_notification = %SentNotification{}, attrs) do sent_notification |> SentNotification.changeset(attrs) |> Repo.update() @@ -523,30 +523,30 @@ defmodule Cadet.Notifications do @doc """ Deletes a sent_notification. - + ## Examples - + iex> delete_sent_notification(sent_notification) {:ok, %SentNotification{}} - + iex> delete_sent_notification(sent_notification) {:error, %Ecto.Changeset{}} - + """ - def delete_sent_notification(%SentNotification{} = sent_notification) do + def delete_sent_notification(sent_notification = %SentNotification{}) do Repo.delete(sent_notification) end @doc """ Returns an `%Ecto.Changeset{}` for tracking sent_notification changes. - + ## Examples - + iex> change_sent_notification(sent_notification) %Ecto.Changeset{data: %SentNotification{}} - + """ - def change_sent_notification(%SentNotification{} = sent_notification, attrs \\ %{}) do + def change_sent_notification(sent_notification = %SentNotification{}, attrs \\ %{}) do SentNotification.changeset(sent_notification, attrs) end end diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex index b430f1a24..9ec8cef12 100644 --- a/lib/cadet/notifications/notification_config.ex +++ b/lib/cadet/notifications/notification_config.ex @@ -1,4 +1,7 @@ defmodule Cadet.Notifications.NotificationConfig do + @moduledoc """ + NotificationConfig entity to store course/assessment configuration for a specific notification type. + """ use Ecto.Schema import Ecto.Changeset alias Cadet.Courses.Course diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index 2085bd62f..97740b0e8 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -1,4 +1,7 @@ defmodule Cadet.Notifications.NotificationPreference do + @moduledoc """ + NotificationPreference entity that stores user preferences for a specific notification for a specific course/assessment. + """ use Ecto.Schema import Ecto.Changeset alias Cadet.Notifications.NotificationConfig diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex index aabbefeed..e4acbc943 100644 --- a/lib/cadet/notifications/notification_type.ex +++ b/lib/cadet/notifications/notification_type.ex @@ -1,4 +1,9 @@ defmodule Cadet.Notifications.NotificationType do + @moduledoc """ + NotificationType entity that represents a unique type of notification that the system supports. + There should only be a single entry of this notification regardless of number of courses/assessments using sending this notification. + Course/assessment specific configuration should exist as NotificationConfig instead. + """ use Ecto.Schema import Ecto.Changeset diff --git a/lib/cadet/notifications/sent_notification.ex b/lib/cadet/notifications/sent_notification.ex index 859b13e2d..362476f41 100644 --- a/lib/cadet/notifications/sent_notification.ex +++ b/lib/cadet/notifications/sent_notification.ex @@ -1,4 +1,7 @@ defmodule Cadet.Notifications.SentNotification do + @moduledoc """ + SentNotification entity to store all sent notifications for logging (and future purposes etc. mailbox) + """ use Ecto.Schema import Ecto.Changeset alias Cadet.Accounts.CourseRegistration diff --git a/lib/cadet/notifications/time_option.ex b/lib/cadet/notifications/time_option.ex index c17010c4a..c72bcf674 100644 --- a/lib/cadet/notifications/time_option.ex +++ b/lib/cadet/notifications/time_option.ex @@ -1,4 +1,7 @@ defmodule Cadet.Notifications.TimeOption do + @moduledoc """ + TimeOption entity for options course admins have created for notifications + """ use Ecto.Schema import Ecto.Changeset alias Cadet.Notifications.NotificationConfig diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index f9b04176c..980412a1d 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -1,25 +1,30 @@ defmodule Cadet.Workers.NotificationWorker do + @moduledoc """ + Contain oban workers for sending notifications + """ use Oban.Worker, queue: :notifications, max_attempts: 1 alias Cadet.Email + alias Cadet.Notifications defp is_system_enabled(notification_type_id) do - Cadet.Notifications.get_notification_type!(notification_type_id).is_enabled + Notifications.get_notification_type!(notification_type_id).is_enabled end defp is_course_enabled(notification_type_id, course_id, assessment_config_id) do - Cadet.Notifications.get_notification_config!( + Notifications.get_notification_config!( notification_type_id, course_id, assessment_config_id ).is_enabled end - def is_user_enabled(notification_type_id, course_reg_id) do - pref = Cadet.Notifications.get_notification_preference(notification_type_id, course_reg_id) + defp is_user_enabled(notification_type_id, course_reg_id) do + pref = Notifications.get_notification_preference(notification_type_id, course_reg_id) - cond do - is_nil(pref) -> true - true -> pref.is_enabled + if is_nil(pref) do + true + else + pref.is_enabled end end @@ -31,17 +36,15 @@ defmodule Cadet.Workers.NotificationWorker do course_reg_id, time_option_minutes ) do - pref = Cadet.Notifications.get_notification_preference(notification_type_id, course_reg_id) + pref = Notifications.get_notification_preference(notification_type_id, course_reg_id) - cond do - is_nil(pref) -> - Cadet.Notifications.get_default_time_option_for_assessment!( - assessment_config_id, - notification_type_id - ).minutes == time_option_minutes - - true -> - pref.time_option.minutes == time_option_minutes + if is_nil(pref) do + Notifications.get_default_time_option_for_assessment!( + assessment_config_id, + notification_type_id + ).minutes == time_option_minutes + else + pref.time_option.minutes == time_option_minutes end end diff --git a/test/cadet_web/controllers/sent_notification_controller_test.exs b/test/cadet_web/controllers/sent_notification_controller_test.exs index 6e3e870b8..9abc02781 100644 --- a/test/cadet_web/controllers/sent_notification_controller_test.exs +++ b/test/cadet_web/controllers/sent_notification_controller_test.exs @@ -89,7 +89,10 @@ defmodule CadetWeb.SentNotificationControllerTest do # describe "delete sent_notification" do # setup [:create_sent_notification] - # test "deletes chosen sent_notification", %{conn: conn, sent_notification: sent_notification} do + # test "deletes chosen sent_notification", %{ + # conn: conn, + # sent_notification: sent_notification + # } do # conn = delete(conn, Routes.sent_notification_path(conn, :delete, sent_notification)) # assert response(conn, 204) From e8bff4c4d1f65f7b618c9e76962af6503a4ebad0 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 27 Feb 2023 17:51:59 +0800 Subject: [PATCH 21/80] chore: Resolve style violations --- lib/cadet/courses/courses.ex | 6 ++--- lib/cadet/email.ex | 4 +++- lib/cadet/notifications.ex | 23 +++++++++---------- .../notifications/notification_config.ex | 3 +-- .../notifications/notification_preference.ex | 3 +-- lib/cadet/workers/NotificationWorker.ex | 3 +-- .../notification_config_controller.ex | 5 +++- .../notification_preference_controller.ex | 5 +++- .../notification_type_controller.ex | 5 +++- .../sent_notification_controller.ex | 5 +++- test/cadet/helpers/model_helper_test.exs | 10 +++----- .../notifications/notifications_test.exs | 12 ++++++---- test/cadet/updater/xml_parser_test.exs | 3 ++- .../notification_config_controller_test.exs | 10 ++++++-- .../notification_type_controller_test.exs | 5 +++- 15 files changed, 61 insertions(+), 41 deletions(-) diff --git a/lib/cadet/courses/courses.ex b/lib/cadet/courses/courses.ex index 701a9c646..5c0464fae 100644 --- a/lib/cadet/courses/courses.ex +++ b/lib/cadet/courses/courses.ex @@ -82,7 +82,7 @@ defmodule Cadet.Courses do end end - def get_all_course_ids() do + def get_all_course_ids do Course |> select([c], c.id) |> Repo.all() @@ -239,8 +239,8 @@ defmodule Cadet.Courses do ) |> where(course_id: ^course_id) |> Repo.one()} do - # It is ok to assume that user course registions already exist, as they would have been created - # in the admin_user_controller before calling this function + # It is ok to assume that user course registions already exist, as they would + # have been created in the admin_user_controller before calling this function case role do # If student, update his course registration :student -> diff --git a/lib/cadet/email.ex b/lib/cadet/email.ex index 014cc37bf..3d471d37a 100644 --- a/lib/cadet/email.ex +++ b/lib/cadet/email.ex @@ -1,4 +1,7 @@ defmodule Cadet.Email do + @moduledoc """ + Contains methods for sending email notifications. + """ use Bamboo.Phoenix, view: CadetWeb.EmailView import Bamboo.Email alias Cadet.Mailer @@ -6,7 +9,6 @@ defmodule Cadet.Email do def avenger_backlog_email(template_file_name, avenger, ungraded_submissions) do if is_nil(avenger.email) do nil - else base_email() |> to(avenger.email) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index bf8714fbe..ecbde3ca5 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -6,8 +6,13 @@ defmodule Cadet.Notifications do import Ecto.Query, warn: false alias Cadet.Repo - alias Cadet.Notifications.NotificationType - alias Cadet.Notifications.NotificationConfig + alias Cadet.Notifications.{ + NotificationType, + NotificationConfig, + SentNotification, + TimeOption, + NotificationPreference + } @doc """ Returns the list of notification_types. @@ -215,8 +220,6 @@ defmodule Cadet.Notifications do NotificationConfig.changeset(notification_config, attrs) end - alias Cadet.Notifications.TimeOption - @doc """ Returns the list of time_options. @@ -306,7 +309,7 @@ defmodule Cadet.Notifications do {:error, %Ecto.Changeset{}} """ - def update_time_option(%TimeOption{} = time_option, attrs) do + def update_time_option(time_option = %TimeOption{}, attrs) do time_option |> TimeOption.changeset(attrs) |> Repo.update() @@ -324,7 +327,7 @@ defmodule Cadet.Notifications do {:error, %Ecto.Changeset{}} """ - def delete_time_option(%TimeOption{} = time_option) do + def delete_time_option(time_option = %TimeOption{}) do Repo.delete(time_option) end @@ -337,12 +340,10 @@ defmodule Cadet.Notifications do %Ecto.Changeset{data: %TimeOption{}} """ - def change_time_option(%TimeOption{} = time_option, attrs \\ %{}) do + def change_time_option(time_option = %TimeOption{}, attrs \\ %{}) do TimeOption.changeset(time_option, attrs) end - alias Cadet.Notifications.NotificationPreference - @doc """ Returns the list of notification_preferences. @@ -448,14 +449,12 @@ defmodule Cadet.Notifications do """ def change_notification_preference( - %NotificationPreference{} = notification_preference, + notification_preference = %NotificationPreference{}, attrs \\ %{} ) do NotificationPreference.changeset(notification_preference, attrs) end - alias Cadet.Notifications.SentNotification - @doc """ Returns the list of sent_notifications. diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex index 9ec8cef12..5e75ab6c5 100644 --- a/lib/cadet/notifications/notification_config.ex +++ b/lib/cadet/notifications/notification_config.ex @@ -4,8 +4,7 @@ defmodule Cadet.Notifications.NotificationConfig do """ use Ecto.Schema import Ecto.Changeset - alias Cadet.Courses.Course - alias Cadet.Courses.AssessmentConfig + alias Cadet.Courses.{Course, AssessmentConfig} alias Cadet.Notifications.NotificationType schema "notification_configs" do diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index 97740b0e8..cad2b8243 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -4,8 +4,7 @@ defmodule Cadet.Notifications.NotificationPreference do """ use Ecto.Schema import Ecto.Changeset - alias Cadet.Notifications.NotificationConfig - alias Cadet.Notifications.TimeOption + alias Cadet.Notifications.{NotificationConfig, TimeOption} alias Cadet.Accounts.CourseRegistration schema "notification_preferences" do diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index 980412a1d..fbad8b33f 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -3,8 +3,7 @@ defmodule Cadet.Workers.NotificationWorker do Contain oban workers for sending notifications """ use Oban.Worker, queue: :notifications, max_attempts: 1 - alias Cadet.Email - alias Cadet.Notifications + alias Cadet.{Email, Notifications} defp is_system_enabled(notification_type_id) do Notifications.get_notification_type!(notification_type_id).is_enabled diff --git a/lib/cadet_web/controllers/notification_config_controller.ex b/lib/cadet_web/controllers/notification_config_controller.ex index e545fc3c7..ddc0a7533 100644 --- a/lib/cadet_web/controllers/notification_config_controller.ex +++ b/lib/cadet_web/controllers/notification_config_controller.ex @@ -16,7 +16,10 @@ defmodule CadetWeb.NotificationConfigController do Notifications.create_notification_config(notification_config_params) do conn |> put_status(:created) - # |> put_resp_header("location", Routes.notification_config_path(conn, :show, notification_config)) + # |> put_resp_header( + # "location", + # Routes.notification_config_path(conn, :show, notification_config) + # ) |> render("show.json", notification_config: notification_config) end end diff --git a/lib/cadet_web/controllers/notification_preference_controller.ex b/lib/cadet_web/controllers/notification_preference_controller.ex index 700ed636b..f9310c553 100644 --- a/lib/cadet_web/controllers/notification_preference_controller.ex +++ b/lib/cadet_web/controllers/notification_preference_controller.ex @@ -16,7 +16,10 @@ defmodule CadetWeb.NotificationPreferenceController do Notifications.create_notification_preference(notification_preference_params) do conn |> put_status(:created) - # |> put_resp_header("location", Routes.notification_preference_path(conn, :show, notification_preference)) + # |> put_resp_header( + # "location", + # Routes.notification_preference_path(conn, :show, notification_preference + # )) |> render("show.json", notification_preference: notification_preference) end end diff --git a/lib/cadet_web/controllers/notification_type_controller.ex b/lib/cadet_web/controllers/notification_type_controller.ex index d199a23f8..471bf452e 100644 --- a/lib/cadet_web/controllers/notification_type_controller.ex +++ b/lib/cadet_web/controllers/notification_type_controller.ex @@ -16,7 +16,10 @@ defmodule CadetWeb.NotificationTypeController do Notifications.create_notification_type(notification_type_params) do conn |> put_status(:created) - # |> put_resp_header("location", Routes.notification_type_path(conn, :show, notification_type)) + # |> put_resp_header( + # "location", + # Routes.notification_type_path(conn, :show, notification_type) + # ) |> render("show.json", notification_type: notification_type) end end diff --git a/lib/cadet_web/controllers/sent_notification_controller.ex b/lib/cadet_web/controllers/sent_notification_controller.ex index 9aba18bae..3d82d6113 100644 --- a/lib/cadet_web/controllers/sent_notification_controller.ex +++ b/lib/cadet_web/controllers/sent_notification_controller.ex @@ -16,7 +16,10 @@ defmodule CadetWeb.SentNotificationController do Notifications.create_sent_notification(sent_notification_params) do conn |> put_status(:created) - # |> put_resp_header("location", Routes.sent_notification_path(conn, :show, sent_notification)) + # |> put_resp_header( + # "location", + # Routes.sent_notification_path(conn, :show, sent_notification) + # ) |> render("show.json", sent_notification: sent_notification) end end diff --git a/test/cadet/helpers/model_helper_test.exs b/test/cadet/helpers/model_helper_test.exs index 7e37ea880..5ae081f31 100644 --- a/test/cadet/helpers/model_helper_test.exs +++ b/test/cadet/helpers/model_helper_test.exs @@ -1,10 +1,10 @@ +alias Ecto.Changeset +use Ecto.Schema + defmodule Cadet.ModelHelperTest.TestObject do @moduledoc """ Test object. """ - use Ecto.Schema - - alias Ecto.Changeset schema "objects" do has_many(:prerequisites, __MODULE__, on_replace: :delete) @@ -25,9 +25,6 @@ defmodule Cadet.ModelHelperTest.TestObjectSpecialKey do """ # Credo false positive due to multiple modules in the file # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse - use Ecto.Schema - - alias Ecto.Changeset @primary_key {:uuid, :binary_id, autogenerate: false} schema "special_objects" do @@ -48,7 +45,6 @@ defmodule Cadet.ModelHelperTest do # Credo false positive due to multiple modules in the file # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse - alias Ecto.Changeset alias Cadet.ModelHelperTest.{TestObject, TestObjectSpecialKey} import Cadet.ModelHelper diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index b715e66be..002b6bf1e 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -66,7 +66,8 @@ defmodule Cadet.NotificationsTest do # test "delete_notification_type/1 deletes the notification_type" do # notification_type = notification_type_fixture() - # assert {:ok, %NotificationType{}} = Notifications.delete_notification_type(notification_type) + # assert {:ok, %NotificationType{}} = + # Notifications.delete_notification_type(notification_type) # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_type!(notification_type.id) end # end @@ -102,7 +103,8 @@ defmodule Cadet.NotificationsTest do # end # test "create_notification_config/1 with invalid data returns error changeset" do - # assert {:error, %Ecto.Changeset{}} = Notifications.create_notification_config(@invalid_attrs) + # assert {:error, %Ecto.Changeset{}} = + # Notifications.create_notification_config(@invalid_attrs) # end # test "update_notification_config/2 with valid data updates the notification_config" do @@ -299,13 +301,15 @@ defmodule Cadet.NotificationsTest do # test "delete_sent_notification/1 deletes the sent_notification" do # sent_notification = sent_notification_fixture() - # assert {:ok, %SentNotification{}} = Notifications.delete_sent_notification(sent_notification) + # assert {:ok, %SentNotification{}} = + # Notifications.delete_sent_notification(sent_notification) # assert_raise Ecto.NoResultsError, fn -> Notifications.get_sent_notification!(sent_notification.id) end # end # test "change_sent_notification/1 returns a sent_notification changeset" do # sent_notification = sent_notification_fixture() - # assert %Ecto.Changeset{} = Notifications.change_sent_notification(sent_notification) + # assert %Ecto.Changeset{} = + # Notifications.change_sent_notification(sent_notification) # end end end diff --git a/test/cadet/updater/xml_parser_test.exs b/test/cadet/updater/xml_parser_test.exs index aefbf6bdb..fb0426e35 100644 --- a/test/cadet/updater/xml_parser_test.exs +++ b/test/cadet/updater/xml_parser_test.exs @@ -39,7 +39,8 @@ defmodule Cadet.Updater.XMLParserTest do ) ) - # contest assessment need to be added before assessment containing voting questions can be added. + # contest assessment need to be added before assessment + # containing voting questions can be added. contest_assessment = insert(:assessment, course: course, config: hd(assessment_configs)) assessments_with_config = Enum.into(assessments, %{}, &{&1, &1.config}) diff --git a/test/cadet_web/controllers/notification_config_controller_test.exs b/test/cadet_web/controllers/notification_config_controller_test.exs index 22ae1559b..54dac060a 100644 --- a/test/cadet_web/controllers/notification_config_controller_test.exs +++ b/test/cadet_web/controllers/notification_config_controller_test.exs @@ -73,7 +73,10 @@ defmodule CadetWeb.NotificationConfigControllerTest do # } = json_response(conn, 200)["data"] # end - # test "renders errors when data is invalid", %{conn: conn, notification_config: notification_config} do + # test "renders errors when data is invalid", %{ + # conn: conn, + # notification_config: notification_config + # } do # conn = put( # conn, # Routes.notification_config_path(conn, :update, notification_config), @@ -86,7 +89,10 @@ defmodule CadetWeb.NotificationConfigControllerTest do # describe "delete notification_config" do # setup [:create_notification_config] - # test "deletes chosen notification_config", %{conn: conn, notification_config: notification_config} do + # test "deletes chosen notification_config", %{ + # conn: conn, + # notification_config: notification_config + # } do # conn = delete(conn, Routes.notification_config_path(conn, :delete, notification_config)) # assert response(conn, 204) diff --git a/test/cadet_web/controllers/notification_type_controller_test.exs b/test/cadet_web/controllers/notification_type_controller_test.exs index d7ea2d90f..a3221eb41 100644 --- a/test/cadet_web/controllers/notification_type_controller_test.exs +++ b/test/cadet_web/controllers/notification_type_controller_test.exs @@ -101,7 +101,10 @@ defmodule CadetWeb.NotificationTypeControllerTest do # describe "delete notification_type" do # setup [:create_notification_type] - # test "deletes chosen notification_type", %{conn: conn, notification_type: notification_type} do + # test "deletes chosen notification_type", %{ + # conn: conn, + # notification_type: notification_type + # } do # conn = delete(conn, Routes.notification_type_path(conn, :delete, notification_type)) # assert response(conn, 204) From 45bbb34fcedf8101b6eaccc3ae4860cd10b591be Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Mon, 27 Feb 2023 13:53:52 +0800 Subject: [PATCH 22/80] style: fix formatting --- lib/cadet/notifications.ex | 230 +++++++++--------- test/cadet/helpers/model_helper_test.exs | 1 + .../notification_type_controller_test.exs | 42 ++-- .../sent_notification_controller_test.exs | 36 +-- .../notifcation_config_factory.ex | 2 +- .../notifications/time_option_factory.ex | 2 +- 6 files changed, 157 insertions(+), 156 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index ecbde3ca5..ba89f24be 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -16,12 +16,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of notification_types. - + ## Examples - + iex> list_notification_types() [%NotificationType{}, ...] - + """ def list_notification_types do Repo.all(NotificationType) @@ -29,31 +29,31 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_type. - + Raises `Ecto.NoResultsError` if the Notification type does not exist. - + ## Examples - + iex> get_notification_type!(123) %NotificationType{} - + iex> get_notification_type!(456) ** (Ecto.NoResultsError) - + """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) @doc """ Creates a notification_type. - + ## Examples - + iex> create_notification_type(%{field: value}) {:ok, %NotificationType{}} - + iex> create_notification_type(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_type(attrs \\ %{}) do %NotificationType{} @@ -63,15 +63,15 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_type. - + ## Examples - + iex> update_notification_type(notification_type, %{field: new_value}) {:ok, %NotificationType{}} - + iex> update_notification_type(notification_type, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_notification_type(notification_type = %NotificationType{}, attrs) do notification_type @@ -81,15 +81,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_type. - + ## Examples - + iex> delete_notification_type(notification_type) {:ok, %NotificationType{}} - + iex> delete_notification_type(notification_type) {:error, %Ecto.Changeset{}} - + """ def delete_notification_type(notification_type = %NotificationType{}) do Repo.delete(notification_type) @@ -97,12 +97,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_type changes. - + ## Examples - + iex> change_notification_type(notification_type) %Ecto.Changeset{data: %NotificationType{}} - + """ def change_notification_type(notification_type = %NotificationType{}, attrs \\ %{}) do NotificationType.changeset(notification_type, attrs) @@ -110,12 +110,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of notification_configs. - + ## Examples - + iex> list_notification_configs() [%NotificationConfig{}, ...] - + """ def list_notification_configs do Repo.all(NotificationConfig) @@ -123,17 +123,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_config. - + Raises `Ecto.NoResultsError` if the Notification config does not exist. - + ## Examples - + iex> get_notification_config!(123) %NotificationConfig{} - + iex> get_notification_config!(456) ** (Ecto.NoResultsError) - + """ def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) @@ -157,15 +157,15 @@ defmodule Cadet.Notifications do @doc """ Creates a notification_config. - + ## Examples - + iex> create_notification_config(%{field: value}) {:ok, %NotificationConfig{}} - + iex> create_notification_config(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_config(attrs \\ %{}) do %NotificationConfig{} @@ -175,15 +175,15 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_config. - + ## Examples - + iex> update_notification_config(notification_config, %{field: new_value}) {:ok, %NotificationConfig{}} - + iex> update_notification_config(notification_config, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_notification_config(notification_config = %NotificationConfig{}, attrs) do notification_config @@ -193,15 +193,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_config. - + ## Examples - + iex> delete_notification_config(notification_config) {:ok, %NotificationConfig{}} - + iex> delete_notification_config(notification_config) {:error, %Ecto.Changeset{}} - + """ def delete_notification_config(notification_config = %NotificationConfig{}) do Repo.delete(notification_config) @@ -209,12 +209,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_config changes. - + ## Examples - + iex> change_notification_config(notification_config) %Ecto.Changeset{data: %NotificationConfig{}} - + """ def change_notification_config(notification_config = %NotificationConfig{}, attrs \\ %{}) do NotificationConfig.changeset(notification_config, attrs) @@ -222,12 +222,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of time_options. - + ## Examples - + iex> list_time_options() [%TimeOption{}, ...] - + """ def list_time_options do Repo.all(TimeOption) @@ -235,17 +235,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single time_option. - + Raises `Ecto.NoResultsError` if the Time option does not exist. - + ## Examples - + iex> get_time_option!(123) %TimeOption{} - + iex> get_time_option!(456) ** (Ecto.NoResultsError) - + """ def get_time_option!(id), do: Repo.get!(TimeOption, id) @@ -281,15 +281,15 @@ defmodule Cadet.Notifications do @doc """ Creates a time_option. - + ## Examples - + iex> create_time_option(%{field: value}) {:ok, %TimeOption{}} - + iex> create_time_option(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_time_option(attrs \\ %{}) do %TimeOption{} @@ -299,15 +299,15 @@ defmodule Cadet.Notifications do @doc """ Updates a time_option. - + ## Examples - + iex> update_time_option(time_option, %{field: new_value}) {:ok, %TimeOption{}} - + iex> update_time_option(time_option, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_time_option(time_option = %TimeOption{}, attrs) do time_option @@ -317,15 +317,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a time_option. - + ## Examples - + iex> delete_time_option(time_option) {:ok, %TimeOption{}} - + iex> delete_time_option(time_option) {:error, %Ecto.Changeset{}} - + """ def delete_time_option(time_option = %TimeOption{}) do Repo.delete(time_option) @@ -333,12 +333,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking time_option changes. - + ## Examples - + iex> change_time_option(time_option) %Ecto.Changeset{data: %TimeOption{}} - + """ def change_time_option(time_option = %TimeOption{}, attrs \\ %{}) do TimeOption.changeset(time_option, attrs) @@ -346,12 +346,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of notification_preferences. - + ## Examples - + iex> list_notification_preferences() [%NotificationPreference{}, ...] - + """ def list_notification_preferences do Repo.all(NotificationPreference) @@ -359,17 +359,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_preference. - + Raises `Ecto.NoResultsError` if the Notification preference does not exist. - + ## Examples - + iex> get_notification_preference!(123) %NotificationPreference{} - + iex> get_notification_preference!(456) ** (Ecto.NoResultsError) - + """ def get_notification_preference!(id), do: Repo.get!(NotificationPreference, id) @@ -389,15 +389,15 @@ defmodule Cadet.Notifications do @doc """ Creates a notification_preference. - + ## Examples - + iex> create_notification_preference(%{field: value}) {:ok, %NotificationPreference{}} - + iex> create_notification_preference(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_preference(attrs \\ %{}) do %NotificationPreference{} @@ -407,15 +407,15 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_preference. - + ## Examples - + iex> update_notification_preference(notification_preference, %{field: new_value}) {:ok, %NotificationPreference{}} - + iex> update_notification_preference(notification_preference, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_notification_preference(notification_preference = %NotificationPreference{}, attrs) do notification_preference @@ -425,15 +425,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_preference. - + ## Examples - + iex> delete_notification_preference(notification_preference) {:ok, %NotificationPreference{}} - + iex> delete_notification_preference(notification_preference) {:error, %Ecto.Changeset{}} - + """ def delete_notification_preference(notification_preference = %NotificationPreference{}) do Repo.delete(notification_preference) @@ -441,12 +441,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_preference changes. - + ## Examples - + iex> change_notification_preference(notification_preference) %Ecto.Changeset{data: %NotificationPreference{}} - + """ def change_notification_preference( notification_preference = %NotificationPreference{}, @@ -457,12 +457,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of sent_notifications. - + ## Examples - + iex> list_sent_notifications() [%SentNotification{}, ...] - + """ def list_sent_notifications do Repo.all(SentNotification) @@ -470,31 +470,31 @@ defmodule Cadet.Notifications do @doc """ Gets a single sent_notification. - + Raises `Ecto.NoResultsError` if the Sent notification does not exist. - + ## Examples - + iex> get_sent_notification!(123) %SentNotification{} - + iex> get_sent_notification!(456) ** (Ecto.NoResultsError) - + """ def get_sent_notification!(id), do: Repo.get!(SentNotification, id) @doc """ Creates a sent_notification. - + ## Examples - + iex> create_sent_notification(%{field: value}) {:ok, %SentNotification{}} - + iex> create_sent_notification(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_sent_notification(attrs \\ %{}) do %SentNotification{} @@ -504,15 +504,15 @@ defmodule Cadet.Notifications do @doc """ Updates a sent_notification. - + ## Examples - + iex> update_sent_notification(sent_notification, %{field: new_value}) {:ok, %SentNotification{}} - + iex> update_sent_notification(sent_notification, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_sent_notification(sent_notification = %SentNotification{}, attrs) do sent_notification @@ -522,15 +522,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a sent_notification. - + ## Examples - + iex> delete_sent_notification(sent_notification) {:ok, %SentNotification{}} - + iex> delete_sent_notification(sent_notification) {:error, %Ecto.Changeset{}} - + """ def delete_sent_notification(sent_notification = %SentNotification{}) do Repo.delete(sent_notification) @@ -538,12 +538,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking sent_notification changes. - + ## Examples - + iex> change_sent_notification(sent_notification) %Ecto.Changeset{data: %SentNotification{}} - + """ def change_sent_notification(sent_notification = %SentNotification{}, attrs \\ %{}) do SentNotification.changeset(sent_notification, attrs) diff --git a/test/cadet/helpers/model_helper_test.exs b/test/cadet/helpers/model_helper_test.exs index 5ae081f31..ae8a39b53 100644 --- a/test/cadet/helpers/model_helper_test.exs +++ b/test/cadet/helpers/model_helper_test.exs @@ -23,6 +23,7 @@ defmodule Cadet.ModelHelperTest.TestObjectSpecialKey do @moduledoc """ Test object. """ + # Credo false positive due to multiple modules in the file # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse diff --git a/test/cadet_web/controllers/notification_type_controller_test.exs b/test/cadet_web/controllers/notification_type_controller_test.exs index a3221eb41..399ac0796 100644 --- a/test/cadet_web/controllers/notification_type_controller_test.exs +++ b/test/cadet_web/controllers/notification_type_controller_test.exs @@ -63,27 +63,27 @@ defmodule CadetWeb.NotificationTypeControllerTest do # describe "update notification_type" do # setup [:create_notification_type] - # test "renders notification_type when data is valid", %{ - # conn: conn, - # notification_type: %NotificationType{id: id} = notification_type - # } do - # conn = put( - # conn, - # Routes.notification_type_path(conn, :update, notification_type), - # notification_type: @update_attrs - # ) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.notification_type_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_autopopulated" => false, - # "is_enabled" => false, - # "name" => "some updated name", - # "template_file_name" => "some updated template_file_name" - # } = json_response(conn, 200)["data"] - # end + # test "renders notification_type when data is valid", %{ + # conn: conn, + # notification_type: %NotificationType{id: id} = notification_type + # } do + # conn = put( + # conn, + # Routes.notification_type_path(conn, :update, notification_type), + # notification_type: @update_attrs + # ) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.notification_type_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "is_autopopulated" => false, + # "is_enabled" => false, + # "name" => "some updated name", + # "template_file_name" => "some updated template_file_name" + # } = json_response(conn, 200)["data"] + # end # test "renders errors when data is invalid", %{ # conn: conn, diff --git a/test/cadet_web/controllers/sent_notification_controller_test.exs b/test/cadet_web/controllers/sent_notification_controller_test.exs index 9abc02781..6f0b5e03a 100644 --- a/test/cadet_web/controllers/sent_notification_controller_test.exs +++ b/test/cadet_web/controllers/sent_notification_controller_test.exs @@ -54,24 +54,24 @@ defmodule CadetWeb.SentNotificationControllerTest do # describe "update sent_notification" do # setup [:create_sent_notification] - # test "renders sent_notification when data is valid", %{ - # conn: conn, - # sent_notification: %SentNotification{id: id} = sent_notification - # } do - # conn = put( - # conn, - # Routes.sent_notification_path(conn, :update, sent_notification), - # sent_notification: @update_attrs - # ) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "content" => "some updated content" - # } = json_response(conn, 200)["data"] - # end + # test "renders sent_notification when data is valid", %{ + # conn: conn, + # sent_notification: %SentNotification{id: id} = sent_notification + # } do + # conn = put( + # conn, + # Routes.sent_notification_path(conn, :update, sent_notification), + # sent_notification: @update_attrs + # ) + # assert %{"id" => ^id} = json_response(conn, 200)["data"] + + # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) + + # assert %{ + # "id" => ^id, + # "content" => "some updated content" + # } = json_response(conn, 200)["data"] + # end # test "renders errors when data is invalid", %{ # conn: conn, diff --git a/test/factories/notifications/notifcation_config_factory.ex b/test/factories/notifications/notifcation_config_factory.ex index 78942100b..43588fa29 100644 --- a/test/factories/notifications/notifcation_config_factory.ex +++ b/test/factories/notifications/notifcation_config_factory.ex @@ -12,7 +12,7 @@ defmodule Cadet.Notifications.NotificationConfigFactory do is_enabled: false, notification_type: build(:notification_type), course: build(:course), - assessment_config: build(:assessment_config), + assessment_config: build(:assessment_config) } end end diff --git a/test/factories/notifications/time_option_factory.ex b/test/factories/notifications/time_option_factory.ex index b659bc3af..d5aa1c898 100644 --- a/test/factories/notifications/time_option_factory.ex +++ b/test/factories/notifications/time_option_factory.ex @@ -11,7 +11,7 @@ defmodule Cadet.Notifications.TimeOptionFactory do %TimeOption{ is_default: false, minutes: 0, - notification_config: build(:notification_config), + notification_config: build(:notification_config) } end end From 960d75bd6b1ca578ad1336b32f3b2fbaab82988b Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Mon, 27 Feb 2023 14:03:57 +0800 Subject: [PATCH 23/80] chore: remove unused controllers and views - remove auto-generated controllers and views that are not used --- .../notification_config_controller.ex | 52 -------- .../notification_preference_controller.ex | 52 -------- .../notification_type_controller.ex | 48 ------- .../sent_notification_controller.ex | 48 ------- .../controllers/time_option_controller.ex | 45 ------- .../views/notification_config_view.ex | 19 --- .../views/notification_preference_view.ex | 33 ----- lib/cadet_web/views/notification_type_view.ex | 22 ---- lib/cadet_web/views/sent_notification_view.ex | 19 --- lib/cadet_web/views/time_option_view.ex | 20 --- .../notification_config_controller_test.exs | 109 ---------------- ...otification_preference_controller_test.exs | 109 ---------------- .../notification_type_controller_test.exs | 121 ------------------ .../sent_notification_controller_test.exs | 109 ---------------- .../time_option_controller_test.exs | 99 -------------- 15 files changed, 905 deletions(-) delete mode 100644 lib/cadet_web/controllers/notification_config_controller.ex delete mode 100644 lib/cadet_web/controllers/notification_preference_controller.ex delete mode 100644 lib/cadet_web/controllers/notification_type_controller.ex delete mode 100644 lib/cadet_web/controllers/sent_notification_controller.ex delete mode 100644 lib/cadet_web/controllers/time_option_controller.ex delete mode 100644 lib/cadet_web/views/notification_config_view.ex delete mode 100644 lib/cadet_web/views/notification_preference_view.ex delete mode 100644 lib/cadet_web/views/notification_type_view.ex delete mode 100644 lib/cadet_web/views/sent_notification_view.ex delete mode 100644 lib/cadet_web/views/time_option_view.ex delete mode 100644 test/cadet_web/controllers/notification_config_controller_test.exs delete mode 100644 test/cadet_web/controllers/notification_preference_controller_test.exs delete mode 100644 test/cadet_web/controllers/notification_type_controller_test.exs delete mode 100644 test/cadet_web/controllers/sent_notification_controller_test.exs delete mode 100644 test/cadet_web/controllers/time_option_controller_test.exs diff --git a/lib/cadet_web/controllers/notification_config_controller.ex b/lib/cadet_web/controllers/notification_config_controller.ex deleted file mode 100644 index ddc0a7533..000000000 --- a/lib/cadet_web/controllers/notification_config_controller.ex +++ /dev/null @@ -1,52 +0,0 @@ -defmodule CadetWeb.NotificationConfigController do - use CadetWeb, :controller - - alias Cadet.Notifications - alias Cadet.Notifications.NotificationConfig - - action_fallback(CadetWeb.FallbackController) - - def index(conn, _params) do - notification_configs = Notifications.list_notification_configs() - render(conn, "index.json", notification_configs: notification_configs) - end - - def create(conn, %{"notification_config" => notification_config_params}) do - with {:ok, %NotificationConfig{} = notification_config} <- - Notifications.create_notification_config(notification_config_params) do - conn - |> put_status(:created) - # |> put_resp_header( - # "location", - # Routes.notification_config_path(conn, :show, notification_config) - # ) - |> render("show.json", notification_config: notification_config) - end - end - - def show(conn, %{"id" => id}) do - notification_config = Notifications.get_notification_config!(id) - render(conn, "show.json", notification_config: notification_config) - end - - def update(conn, %{"id" => id, "notification_config" => notification_config_params}) do - notification_config = Notifications.get_notification_config!(id) - - with {:ok, %NotificationConfig{} = notification_config} <- - Notifications.update_notification_config( - notification_config, - notification_config_params - ) do - render(conn, "show.json", notification_config: notification_config) - end - end - - def delete(conn, %{"id" => id}) do - notification_config = Notifications.get_notification_config!(id) - - with {:ok, %NotificationConfig{}} <- - Notifications.delete_notification_config(notification_config) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/cadet_web/controllers/notification_preference_controller.ex b/lib/cadet_web/controllers/notification_preference_controller.ex deleted file mode 100644 index f9310c553..000000000 --- a/lib/cadet_web/controllers/notification_preference_controller.ex +++ /dev/null @@ -1,52 +0,0 @@ -defmodule CadetWeb.NotificationPreferenceController do - use CadetWeb, :controller - - alias Cadet.Notifications - alias Cadet.Notifications.NotificationPreference - - # action_fallback CadetWeb.FallbackController - - def index(conn, _params) do - notification_preferences = Notifications.list_notification_preferences() - render(conn, "index.json", notification_preferences: notification_preferences) - end - - def create(conn, %{"notification_preference" => notification_preference_params}) do - with {:ok, %NotificationPreference{} = notification_preference} <- - Notifications.create_notification_preference(notification_preference_params) do - conn - |> put_status(:created) - # |> put_resp_header( - # "location", - # Routes.notification_preference_path(conn, :show, notification_preference - # )) - |> render("show.json", notification_preference: notification_preference) - end - end - - def show(conn, %{"id" => id}) do - notification_preference = Notifications.get_notification_preference!(id) - render(conn, "show.json", notification_preference: notification_preference) - end - - def update(conn, %{"id" => id, "notification_preference" => notification_preference_params}) do - notification_preference = Notifications.get_notification_preference!(id) - - with {:ok, %NotificationPreference{} = notification_preference} <- - Notifications.update_notification_preference( - notification_preference, - notification_preference_params - ) do - render(conn, "show.json", notification_preference: notification_preference) - end - end - - def delete(conn, %{"id" => id}) do - notification_preference = Notifications.get_notification_preference!(id) - - with {:ok, %NotificationPreference{}} <- - Notifications.delete_notification_preference(notification_preference) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/cadet_web/controllers/notification_type_controller.ex b/lib/cadet_web/controllers/notification_type_controller.ex deleted file mode 100644 index 471bf452e..000000000 --- a/lib/cadet_web/controllers/notification_type_controller.ex +++ /dev/null @@ -1,48 +0,0 @@ -defmodule CadetWeb.NotificationTypeController do - use CadetWeb, :controller - - alias Cadet.Notifications - alias Cadet.Notifications.NotificationType - - # action_fallback(CadetWeb.FallbackController) - - def index(conn, _params) do - notification_types = Notifications.list_notification_types() - render(conn, "index.json", notification_types: notification_types) - end - - def create(conn, %{"notification_type" => notification_type_params}) do - with {:ok, %NotificationType{} = notification_type} <- - Notifications.create_notification_type(notification_type_params) do - conn - |> put_status(:created) - # |> put_resp_header( - # "location", - # Routes.notification_type_path(conn, :show, notification_type) - # ) - |> render("show.json", notification_type: notification_type) - end - end - - def show(conn, %{"id" => id}) do - notification_type = Notifications.get_notification_type!(id) - render(conn, "show.json", notification_type: notification_type) - end - - def update(conn, %{"id" => id, "notification_type" => notification_type_params}) do - notification_type = Notifications.get_notification_type!(id) - - with {:ok, %NotificationType{} = notification_type} <- - Notifications.update_notification_type(notification_type, notification_type_params) do - render(conn, "show.json", notification_type: notification_type) - end - end - - def delete(conn, %{"id" => id}) do - notification_type = Notifications.get_notification_type!(id) - - with {:ok, %NotificationType{}} <- Notifications.delete_notification_type(notification_type) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/cadet_web/controllers/sent_notification_controller.ex b/lib/cadet_web/controllers/sent_notification_controller.ex deleted file mode 100644 index 3d82d6113..000000000 --- a/lib/cadet_web/controllers/sent_notification_controller.ex +++ /dev/null @@ -1,48 +0,0 @@ -defmodule CadetWeb.SentNotificationController do - use CadetWeb, :controller - - alias Cadet.Notifications - alias Cadet.Notifications.SentNotification - - # action_fallback CadetWeb.FallbackController - - def index(conn, _params) do - sent_notifications = Notifications.list_sent_notifications() - render(conn, "index.json", sent_notifications: sent_notifications) - end - - def create(conn, %{"sent_notification" => sent_notification_params}) do - with {:ok, %SentNotification{} = sent_notification} <- - Notifications.create_sent_notification(sent_notification_params) do - conn - |> put_status(:created) - # |> put_resp_header( - # "location", - # Routes.sent_notification_path(conn, :show, sent_notification) - # ) - |> render("show.json", sent_notification: sent_notification) - end - end - - def show(conn, %{"id" => id}) do - sent_notification = Notifications.get_sent_notification!(id) - render(conn, "show.json", sent_notification: sent_notification) - end - - def update(conn, %{"id" => id, "sent_notification" => sent_notification_params}) do - sent_notification = Notifications.get_sent_notification!(id) - - with {:ok, %SentNotification{} = sent_notification} <- - Notifications.update_sent_notification(sent_notification, sent_notification_params) do - render(conn, "show.json", sent_notification: sent_notification) - end - end - - def delete(conn, %{"id" => id}) do - sent_notification = Notifications.get_sent_notification!(id) - - with {:ok, %SentNotification{}} <- Notifications.delete_sent_notification(sent_notification) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/cadet_web/controllers/time_option_controller.ex b/lib/cadet_web/controllers/time_option_controller.ex deleted file mode 100644 index e509ccdbf..000000000 --- a/lib/cadet_web/controllers/time_option_controller.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule CadetWeb.TimeOptionController do - use CadetWeb, :controller - - alias Cadet.Notifications - alias Cadet.Notifications.TimeOption - - # action_fallback CadetWeb.FallbackController - - def index(conn, _params) do - time_options = Notifications.list_time_options() - render(conn, "index.json", time_options: time_options) - end - - def create(conn, %{"time_option" => time_option_params}) do - with {:ok, %TimeOption{} = time_option} <- - Notifications.create_time_option(time_option_params) do - conn - |> put_status(:created) - # |> put_resp_header("location", Routes.time_option_path(conn, :show, time_option)) - |> render("show.json", time_option: time_option) - end - end - - def show(conn, %{"id" => id}) do - time_option = Notifications.get_time_option!(id) - render(conn, "show.json", time_option: time_option) - end - - def update(conn, %{"id" => id, "time_option" => time_option_params}) do - time_option = Notifications.get_time_option!(id) - - with {:ok, %TimeOption{} = time_option} <- - Notifications.update_time_option(time_option, time_option_params) do - render(conn, "show.json", time_option: time_option) - end - end - - def delete(conn, %{"id" => id}) do - time_option = Notifications.get_time_option!(id) - - with {:ok, %TimeOption{}} <- Notifications.delete_time_option(time_option) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/cadet_web/views/notification_config_view.ex b/lib/cadet_web/views/notification_config_view.ex deleted file mode 100644 index f231cd1ab..000000000 --- a/lib/cadet_web/views/notification_config_view.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule CadetWeb.NotificationConfigView do - use CadetWeb, :view - alias CadetWeb.NotificationConfigView - - def render("index.json", %{notification_configs: notification_configs}) do - %{data: render_many(notification_configs, NotificationConfigView, "notification_config.json")} - end - - def render("show.json", %{notification_config: notification_config}) do - %{data: render_one(notification_config, NotificationConfigView, "notification_config.json")} - end - - def render("notification_config.json", %{notification_config: notification_config}) do - %{ - id: notification_config.id, - is_enabled: notification_config.is_enabled - } - end -end diff --git a/lib/cadet_web/views/notification_preference_view.ex b/lib/cadet_web/views/notification_preference_view.ex deleted file mode 100644 index dfe0db752..000000000 --- a/lib/cadet_web/views/notification_preference_view.ex +++ /dev/null @@ -1,33 +0,0 @@ -defmodule CadetWeb.NotificationPreferenceView do - use CadetWeb, :view - alias CadetWeb.NotificationPreferenceView - - def render("index.json", %{notification_preferences: notification_preferences}) do - %{ - data: - render_many( - notification_preferences, - NotificationPreferenceView, - "notification_preference.json" - ) - } - end - - def render("show.json", %{notification_preference: notification_preference}) do - %{ - data: - render_one( - notification_preference, - NotificationPreferenceView, - "notification_preference.json" - ) - } - end - - def render("notification_preference.json", %{notification_preference: notification_preference}) do - %{ - id: notification_preference.id, - is_enabled: notification_preference.is_enabled - } - end -end diff --git a/lib/cadet_web/views/notification_type_view.ex b/lib/cadet_web/views/notification_type_view.ex deleted file mode 100644 index 036b7085c..000000000 --- a/lib/cadet_web/views/notification_type_view.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule CadetWeb.NotificationTypeView do - use CadetWeb, :view - alias CadetWeb.NotificationTypeView - - def render("index.json", %{notification_types: notification_types}) do - %{data: render_many(notification_types, NotificationTypeView, "notification_type.json")} - end - - def render("show.json", %{notification_type: notification_type}) do - %{data: render_one(notification_type, NotificationTypeView, "notification_type.json")} - end - - def render("notification_type.json", %{notification_type: notification_type}) do - %{ - id: notification_type.id, - name: notification_type.name, - template_file_name: notification_type.template_file_name, - is_enabled: notification_type.is_enabled, - is_autopopulated: notification_type.is_autopopulated - } - end -end diff --git a/lib/cadet_web/views/sent_notification_view.ex b/lib/cadet_web/views/sent_notification_view.ex deleted file mode 100644 index b0e3d101e..000000000 --- a/lib/cadet_web/views/sent_notification_view.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule CadetWeb.SentNotificationView do - use CadetWeb, :view - alias CadetWeb.SentNotificationView - - def render("index.json", %{sent_notifications: sent_notifications}) do - %{data: render_many(sent_notifications, SentNotificationView, "sent_notification.json")} - end - - def render("show.json", %{sent_notification: sent_notification}) do - %{data: render_one(sent_notification, SentNotificationView, "sent_notification.json")} - end - - def render("sent_notification.json", %{sent_notification: sent_notification}) do - %{ - id: sent_notification.id, - content: sent_notification.content - } - end -end diff --git a/lib/cadet_web/views/time_option_view.ex b/lib/cadet_web/views/time_option_view.ex deleted file mode 100644 index 558864a25..000000000 --- a/lib/cadet_web/views/time_option_view.ex +++ /dev/null @@ -1,20 +0,0 @@ -defmodule CadetWeb.TimeOptionView do - use CadetWeb, :view - alias CadetWeb.TimeOptionView - - def render("index.json", %{time_options: time_options}) do - %{data: render_many(time_options, TimeOptionView, "time_option.json")} - end - - def render("show.json", %{time_option: time_option}) do - %{data: render_one(time_option, TimeOptionView, "time_option.json")} - end - - def render("time_option.json", %{time_option: time_option}) do - %{ - id: time_option.id, - minutes: time_option.minutes, - is_default: time_option.is_default - } - end -end diff --git a/test/cadet_web/controllers/notification_config_controller_test.exs b/test/cadet_web/controllers/notification_config_controller_test.exs deleted file mode 100644 index 54dac060a..000000000 --- a/test/cadet_web/controllers/notification_config_controller_test.exs +++ /dev/null @@ -1,109 +0,0 @@ -defmodule CadetWeb.NotificationConfigControllerTest do - use CadetWeb.ConnCase - - # import Cadet.NotificationsFixtures - - # alias Cadet.Notifications.NotificationConfig - - # @create_attrs %{ - # is_enabled: true - # } - # @update_attrs %{ - # is_enabled: false - # } - # @invalid_attrs %{is_enabled: nil} - - # setup %{conn: conn} do - # {:ok, conn: put_req_header(conn, "accept", "application/json")} - # end - - # describe "index" do - # test "lists all notification_configs", %{conn: conn} do - # conn = get(conn, Routes.notification_config_path(conn, :index)) - # assert json_response(conn, 200)["data"] == [] - # end - # end - - # describe "create notification_config" do - # test "renders notification_config when data is valid", %{conn: conn} do - # conn = post( - # conn, - # Routes.notification_config_path(conn, :create), - # notification_config: @create_attrs - # ) - # assert %{"id" => id} = json_response(conn, 201)["data"] - - # conn = get(conn, Routes.notification_config_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_enabled" => true - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn} do - # conn = post( - # conn, - # Routes.notification_config_path(conn, :create), - # notification_config: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "update notification_config" do - # setup [:create_notification_config] - - # test "renders notification_config when data is valid", %{ - # conn: conn, - # notification_config: %NotificationConfig{id: id} = notification_config - # } do - # conn = put( - # conn, - # Routes.notification_config_path(conn, :update, notification_config), - # notification_config: @update_attrs - # ) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.notification_config_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_enabled" => false - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{ - # conn: conn, - # notification_config: notification_config - # } do - # conn = put( - # conn, - # Routes.notification_config_path(conn, :update, notification_config), - # notification_config: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "delete notification_config" do - # setup [:create_notification_config] - - # test "deletes chosen notification_config", %{ - # conn: conn, - # notification_config: notification_config - # } do - # conn = delete(conn, Routes.notification_config_path(conn, :delete, notification_config)) - # assert response(conn, 204) - - # assert_error_sent 404, fn -> - # get(conn, Routes.notification_config_path(conn, :show, notification_config)) - # end - # end - # end - - # defp create_notification_config(_) do - # notification_config = notification_config_fixture() - # %{notification_config: notification_config} - # end -end diff --git a/test/cadet_web/controllers/notification_preference_controller_test.exs b/test/cadet_web/controllers/notification_preference_controller_test.exs deleted file mode 100644 index 6610512a3..000000000 --- a/test/cadet_web/controllers/notification_preference_controller_test.exs +++ /dev/null @@ -1,109 +0,0 @@ -defmodule CadetWeb.NotificationPreferenceControllerTest do - use CadetWeb.ConnCase - - # import Cadet.NotificationsFixtures - - # alias Cadet.Notifications.NotificationPreference - - # @create_attrs %{ - # is_enabled: true - # } - # @update_attrs %{ - # is_enabled: false - # } - # @invalid_attrs %{is_enabled: nil} - - # setup %{conn: conn} do - # {:ok, conn: put_req_header(conn, "accept", "application/json")} - # end - - # describe "index" do - # test "lists all notification_preferences", %{conn: conn} do - # conn = get(conn, Routes.notification_preference_path(conn, :index)) - # assert json_response(conn, 200)["data"] == [] - # end - # end - - # describe "create notification_preference" do - # test "renders notification_preference when data is valid", %{conn: conn} do - # conn = post(conn, Routes.notification_preference_path(conn, :create), - # notification_preference: @create_attrs) - # assert %{"id" => id} = json_response(conn, 201)["data"] - - # conn = get(conn, Routes.notification_preference_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_enabled" => true - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn} do - # conn = post( - # conn, - # Routes.notification_preference_path(conn, :create), - # notification_preference: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "update notification_preference" do - # setup [:create_notification_preference] - - # test "renders notification_preference when data is valid", %{ - # conn: conn, - # notification_preference: %NotificationPreference{id: id} = notification_preference - # } do - # conn = put( - # conn, - # Routes.notification_preference_path(conn, :update, notification_preference), - # notification_preference: @update_attrs - # ) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.notification_preference_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_enabled" => false - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{ - # conn: conn, - # notification_preference: notification_preference - # } do - # conn = put( - # conn, - # Routes.notification_preference_path(conn, :update, notification_preference), - # notification_preference: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "delete notification_preference" do - # setup [:create_notification_preference] - - # test "deletes chosen notification_preference", %{ - # conn: conn, - # notification_preference: notification_preference - # } do - # conn = delete( - # conn, - # Routes.notification_preference_path(conn, :delete, notification_preference) - # ) - # assert response(conn, 204) - - # assert_error_sent 404, fn -> - # get(conn, Routes.notification_preference_path(conn, :show, notification_preference)) - # end - # end - # end - - # defp create_notification_preference(_) do - # notification_preference = notification_preference_fixture() - # %{notification_preference: notification_preference} - # end -end diff --git a/test/cadet_web/controllers/notification_type_controller_test.exs b/test/cadet_web/controllers/notification_type_controller_test.exs deleted file mode 100644 index 399ac0796..000000000 --- a/test/cadet_web/controllers/notification_type_controller_test.exs +++ /dev/null @@ -1,121 +0,0 @@ -defmodule CadetWeb.NotificationTypeControllerTest do - use CadetWeb.ConnCase - - # import Cadet.NotificationsFixtures - - # alias Cadet.Notifications.NotificationType - - # @create_attrs %{ - # is_autopopulated: true, - # is_enabled: true, - # name: "some name", - # template_file_name: "some template_file_name" - # } - # @update_attrs %{ - # is_autopopulated: false, - # is_enabled: false, - # name: "some updated name", - # template_file_name: "some updated template_file_name" - # } - # @invalid_attrs %{is_autopopulated: nil, is_enabled: nil, name: nil, template_file_name: nil} - - # setup %{conn: conn} do - # {:ok, conn: put_req_header(conn, "accept", "application/json")} - # end - - # describe "index" do - # test "lists all notification_types", %{conn: conn} do - # conn = get(conn, Routes.notification_type_path(conn, :index)) - # assert json_response(conn, 200)["data"] == [] - # end - # end - - # describe "create notification_type" do - # test "renders notification_type when data is valid", %{conn: conn} do - # conn = post( - # conn, - # Routes.notification_type_path(conn, :create), - # notification_type: @create_attrs - # ) - # assert %{"id" => id} = json_response(conn, 201)["data"] - - # conn = get(conn, Routes.notification_type_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_autopopulated" => true, - # "is_enabled" => true, - # "name" => "some name", - # "template_file_name" => "some template_file_name" - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn} do - # conn = post( - # conn, - # Routes.notification_type_path(conn, :create), - # notification_type: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "update notification_type" do - # setup [:create_notification_type] - - # test "renders notification_type when data is valid", %{ - # conn: conn, - # notification_type: %NotificationType{id: id} = notification_type - # } do - # conn = put( - # conn, - # Routes.notification_type_path(conn, :update, notification_type), - # notification_type: @update_attrs - # ) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.notification_type_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_autopopulated" => false, - # "is_enabled" => false, - # "name" => "some updated name", - # "template_file_name" => "some updated template_file_name" - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{ - # conn: conn, - # notification_type: notification_type - # } do - # conn = put( - # conn, - # Routes.notification_type_path(conn, :update, notification_type), - # notification_type: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "delete notification_type" do - # setup [:create_notification_type] - - # test "deletes chosen notification_type", %{ - # conn: conn, - # notification_type: notification_type - # } do - # conn = delete(conn, Routes.notification_type_path(conn, :delete, notification_type)) - # assert response(conn, 204) - - # assert_error_sent 404, fn -> - # get(conn, Routes.notification_type_path(conn, :show, notification_type)) - # end - # end - # end - - # defp create_notification_type(_) do - # notification_type = notification_type_fixture() - # %{notification_type: notification_type} - # end -end diff --git a/test/cadet_web/controllers/sent_notification_controller_test.exs b/test/cadet_web/controllers/sent_notification_controller_test.exs deleted file mode 100644 index 6f0b5e03a..000000000 --- a/test/cadet_web/controllers/sent_notification_controller_test.exs +++ /dev/null @@ -1,109 +0,0 @@ -defmodule CadetWeb.SentNotificationControllerTest do - use CadetWeb.ConnCase - - # import Cadet.NotificationsFixtures - - # alias Cadet.Notifications.SentNotification - - # @create_attrs %{ - # content: "some content" - # } - # @update_attrs %{ - # content: "some updated content" - # } - # @invalid_attrs %{content: nil} - - # setup %{conn: conn} do - # {:ok, conn: put_req_header(conn, "accept", "application/json")} - # end - - # describe "index" do - # test "lists all sent_notifications", %{conn: conn} do - # conn = get(conn, Routes.sent_notification_path(conn, :index)) - # assert json_response(conn, 200)["data"] == [] - # end - # end - - # describe "create sent_notification" do - # test "renders sent_notification when data is valid", %{conn: conn} do - # conn = post( - # conn, - # Routes.sent_notification_path(conn, :create), - # sent_notification: @create_attrs - # ) - # assert %{"id" => id} = json_response(conn, 201)["data"] - - # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "content" => "some content" - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn} do - # conn = post( - # conn, - # Routes.sent_notification_path(conn, :create), - # sent_notification: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "update sent_notification" do - # setup [:create_sent_notification] - - # test "renders sent_notification when data is valid", %{ - # conn: conn, - # sent_notification: %SentNotification{id: id} = sent_notification - # } do - # conn = put( - # conn, - # Routes.sent_notification_path(conn, :update, sent_notification), - # sent_notification: @update_attrs - # ) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.sent_notification_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "content" => "some updated content" - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{ - # conn: conn, - # sent_notification: sent_notification - # } do - # conn = put( - # conn, - # Routes.sent_notification_path(conn, :update, sent_notification), - # sent_notification: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "delete sent_notification" do - # setup [:create_sent_notification] - - # test "deletes chosen sent_notification", %{ - # conn: conn, - # sent_notification: sent_notification - # } do - # conn = delete(conn, Routes.sent_notification_path(conn, :delete, sent_notification)) - # assert response(conn, 204) - - # assert_error_sent 404, fn -> - # get(conn, Routes.sent_notification_path(conn, :show, sent_notification)) - # end - # end - # end - - # defp create_sent_notification(_) do - # sent_notification = sent_notification_fixture() - # %{sent_notification: sent_notification} - # end -end diff --git a/test/cadet_web/controllers/time_option_controller_test.exs b/test/cadet_web/controllers/time_option_controller_test.exs deleted file mode 100644 index bcf46d9f9..000000000 --- a/test/cadet_web/controllers/time_option_controller_test.exs +++ /dev/null @@ -1,99 +0,0 @@ -defmodule CadetWeb.TimeOptionControllerTest do - use CadetWeb.ConnCase - - # import Cadet.NotificationsFixtures - - # alias Cadet.Notifications.TimeOption - - # @create_attrs %{ - # is_default: true, - # minutes: 42 - # } - # @update_attrs %{ - # is_default: false, - # minutes: 43 - # } - # @invalid_attrs %{is_default: nil, minutes: nil} - - # setup %{conn: conn} do - # {:ok, conn: put_req_header(conn, "accept", "application/json")} - # end - - # describe "index" do - # test "lists all time_options", %{conn: conn} do - # conn = get(conn, Routes.time_option_path(conn, :index)) - # assert json_response(conn, 200)["data"] == [] - # end - # end - - # describe "create time_option" do - # test "renders time_option when data is valid", %{conn: conn} do - # conn = post(conn, Routes.time_option_path(conn, :create), time_option: @create_attrs) - # assert %{"id" => id} = json_response(conn, 201)["data"] - - # conn = get(conn, Routes.time_option_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_default" => true, - # "minutes" => 42 - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn} do - # conn = post(conn, Routes.time_option_path(conn, :create), time_option: @invalid_attrs) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "update time_option" do - # setup [:create_time_option] - - # test "renders time_option when data is valid", %{ - # conn: conn, - # time_option: %TimeOption{id: id} = time_option - # } do - # conn = put( - # conn, - # Routes.time_option_path(conn, :update, time_option), - # time_option: @update_attrs - # ) - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - - # conn = get(conn, Routes.time_option_path(conn, :show, id)) - - # assert %{ - # "id" => ^id, - # "is_default" => false, - # "minutes" => 43 - # } = json_response(conn, 200)["data"] - # end - - # test "renders errors when data is invalid", %{conn: conn, time_option: time_option} do - # conn = put( - # conn, - # Routes.time_option_path(conn, :update, time_option), - # time_option: @invalid_attrs - # ) - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - # describe "delete time_option" do - # setup [:create_time_option] - - # test "deletes chosen time_option", %{conn: conn, time_option: time_option} do - # conn = delete(conn, Routes.time_option_path(conn, :delete, time_option)) - # assert response(conn, 204) - - # assert_error_sent 404, fn -> - # get(conn, Routes.time_option_path(conn, :show, time_option)) - # end - # end - # end - - # defp create_time_option(_) do - # time_option = time_option_fixture() - # %{time_option: time_option} - # end -end From f6857b996340232a25018afe6bba49fa33cb5da7 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:04:32 +0800 Subject: [PATCH 24/80] fix: Fix bad refactoring --- test/cadet/helpers/model_helper_test.exs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/cadet/helpers/model_helper_test.exs b/test/cadet/helpers/model_helper_test.exs index ae8a39b53..7e37ea880 100644 --- a/test/cadet/helpers/model_helper_test.exs +++ b/test/cadet/helpers/model_helper_test.exs @@ -1,10 +1,10 @@ -alias Ecto.Changeset -use Ecto.Schema - defmodule Cadet.ModelHelperTest.TestObject do @moduledoc """ Test object. """ + use Ecto.Schema + + alias Ecto.Changeset schema "objects" do has_many(:prerequisites, __MODULE__, on_replace: :delete) @@ -23,9 +23,11 @@ defmodule Cadet.ModelHelperTest.TestObjectSpecialKey do @moduledoc """ Test object. """ - # Credo false positive due to multiple modules in the file # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse + use Ecto.Schema + + alias Ecto.Changeset @primary_key {:uuid, :binary_id, autogenerate: false} schema "special_objects" do @@ -46,6 +48,7 @@ defmodule Cadet.ModelHelperTest do # Credo false positive due to multiple modules in the file # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse + alias Ecto.Changeset alias Cadet.ModelHelperTest.{TestObject, TestObjectSpecialKey} import Cadet.ModelHelper From f709625798d7d7cb05d8f3f6f422a5c61d2383a2 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:06:29 +0800 Subject: [PATCH 25/80] fix: Fix testing environment with Oban and Bamboo --- config/test.exs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/test.exs b/config/test.exs index eec4fc18f..5d3245a27 100644 --- a/config/test.exs +++ b/config/test.exs @@ -88,3 +88,10 @@ config :arc, storage: Arc.Storage.Local if "test.secrets.exs" |> Path.expand(__DIR__) |> File.exists?(), do: import_config("test.secrets.exs") + +config :cadet, Oban, + repo: Cadet.Repo, + plugins: false, + queues: false + +config :cadet, Cadet.Mailer, adapter: Bamboo.TestAdapter From b0000c6e4f487491a8a211110bf47aba724a83e6 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Tue, 28 Feb 2023 22:50:17 +0800 Subject: [PATCH 26/80] chore: Update constraints and changesets for notification models --- lib/cadet/notifications/notification_config.ex | 2 +- lib/cadet/notifications/notification_preference.ex | 2 +- lib/cadet/notifications/notification_type.ex | 1 + lib/cadet/notifications/sent_notification.ex | 4 ++-- lib/cadet/notifications/time_option.ex | 3 +-- .../migrations/20230214065925_create_notification_types.exs | 6 ++++-- .../20230214140555_create_notification_preferences.exs | 4 ++-- .../migrations/20230214143617_create_sent_notifications.exs | 2 +- 8 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex index 5e75ab6c5..c9e6505e6 100644 --- a/lib/cadet/notifications/notification_config.ex +++ b/lib/cadet/notifications/notification_config.ex @@ -21,6 +21,6 @@ defmodule Cadet.Notifications.NotificationConfig do def changeset(notification_config, attrs) do notification_config |> cast(attrs, [:is_enabled, :notification_type_id, :course_id]) - |> validate_required([:is_enabled, :notification_type_id, :course_id]) + |> validate_required([:notification_type_id, :course_id]) end end diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index cad2b8243..e98e6e034 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -21,6 +21,6 @@ defmodule Cadet.Notifications.NotificationPreference do def changeset(notification_preference, attrs) do notification_preference |> cast(attrs, [:is_enabled, :notification_config_id, :course_reg_id]) - |> validate_required([:is_enabled, :notification_config_id, :course_reg_id]) + |> validate_required([:notification_config_id, :course_reg_id]) end end diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex index e4acbc943..377864314 100644 --- a/lib/cadet/notifications/notification_type.ex +++ b/lib/cadet/notifications/notification_type.ex @@ -21,5 +21,6 @@ defmodule Cadet.Notifications.NotificationType do notification_type |> cast(attrs, [:name, :template_file_name, :is_enabled, :is_autopopulated]) |> validate_required([:name, :template_file_name, :is_enabled, :is_autopopulated]) + |> unique_constraint(:name) end end diff --git a/lib/cadet/notifications/sent_notification.ex b/lib/cadet/notifications/sent_notification.ex index 362476f41..fb52b75af 100644 --- a/lib/cadet/notifications/sent_notification.ex +++ b/lib/cadet/notifications/sent_notification.ex @@ -17,7 +17,7 @@ defmodule Cadet.Notifications.SentNotification do @doc false def changeset(sent_notification, attrs) do sent_notification - |> cast(attrs, [:content]) - |> validate_required([:content]) + |> cast(attrs, [:content, :course_reg_id]) + |> validate_required([:content, :course_reg_id]) end end diff --git a/lib/cadet/notifications/time_option.ex b/lib/cadet/notifications/time_option.ex index c72bcf674..39459ba92 100644 --- a/lib/cadet/notifications/time_option.ex +++ b/lib/cadet/notifications/time_option.ex @@ -8,7 +8,7 @@ defmodule Cadet.Notifications.TimeOption do schema "time_options" do field(:is_default, :boolean, default: false) - field(:minutes, :integer) + field(:minutes, :integer, default: nil) belongs_to(:notification_config, NotificationConfig) @@ -19,6 +19,5 @@ defmodule Cadet.Notifications.TimeOption do def changeset(time_option, attrs) do time_option |> cast(attrs, [:minutes, :is_default]) - |> validate_required([:minutes, :is_default]) end end diff --git a/priv/repo/migrations/20230214065925_create_notification_types.exs b/priv/repo/migrations/20230214065925_create_notification_types.exs index a9ebfada1..1fcd57d48 100644 --- a/priv/repo/migrations/20230214065925_create_notification_types.exs +++ b/priv/repo/migrations/20230214065925_create_notification_types.exs @@ -3,12 +3,14 @@ defmodule Cadet.Repo.Migrations.CreateNotificationTypes do def change do create table(:notification_types) do - add(:name, :string) - add(:template_file_name, :string) + add(:name, :string, null: false) + add(:template_file_name, :string, null: false) add(:is_enabled, :boolean, default: false, null: false) add(:is_autopopulated, :boolean, default: false, null: false) timestamps() end + + create unique_index(:notification_types, [:name]) end end diff --git a/priv/repo/migrations/20230214140555_create_notification_preferences.exs b/priv/repo/migrations/20230214140555_create_notification_preferences.exs index 77e44c31d..bd7ffc26b 100644 --- a/priv/repo/migrations/20230214140555_create_notification_preferences.exs +++ b/priv/repo/migrations/20230214140555_create_notification_preferences.exs @@ -5,11 +5,11 @@ defmodule Cadet.Repo.Migrations.CreateNotificationPreferences do create table(:notification_preferences) do add(:is_enabled, :boolean, default: false, null: false) - add(:notification_config_id, references(:notification_configs, on_delete: :delete_all), + add(:notification_config_id, references(:notification_configs, on_delete: :delete_all, null: false), null: false ) - add(:time_option_id, references(:time_options, on_delete: :nothing)) + add(:time_option_id, references(:time_options, on_delete: :nothing), default: nil) add(:course_reg_id, references(:course_registrations, on_delete: :delete_all), null: false) timestamps() diff --git a/priv/repo/migrations/20230214143617_create_sent_notifications.exs b/priv/repo/migrations/20230214143617_create_sent_notifications.exs index 276dd16dd..b556f9246 100644 --- a/priv/repo/migrations/20230214143617_create_sent_notifications.exs +++ b/priv/repo/migrations/20230214143617_create_sent_notifications.exs @@ -4,7 +4,7 @@ defmodule Cadet.Repo.Migrations.CreateSentNotifications do def change do create table(:sent_notifications) do add(:content, :text) - add(:course_reg_id, references(:course_registrations, on_delete: :nothing)) + add(:course_reg_id, references(:course_registrations, on_delete: :nothing), null: false) timestamps() end From 7003af51f659353096b8b6309150c10088831425 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 1 Mar 2023 00:28:58 +0800 Subject: [PATCH 27/80] chore: Update default behaviour for no time_option in user preference If user preference has no time option, use the time_option from notification_config instead. This is so that the behaviour of these users with no preferences would always follow the default chosen by the course admin --- lib/cadet/workers/NotificationWorker.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index fbad8b33f..540dd694e 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -37,7 +37,7 @@ defmodule Cadet.Workers.NotificationWorker do ) do pref = Notifications.get_notification_preference(notification_type_id, course_reg_id) - if is_nil(pref) do + if is_nil(pref) or is_nil(pref.time_option) do Notifications.get_default_time_option_for_assessment!( assessment_config_id, notification_type_id From 1f25fc0bd7a53969483ff9833432f505d1a80a70 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 28 Feb 2023 18:34:18 +0800 Subject: [PATCH 28/80] style: fix formatting --- .../migrations/20230214065925_create_notification_types.exs | 2 +- .../20230214140555_create_notification_preferences.exs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/priv/repo/migrations/20230214065925_create_notification_types.exs b/priv/repo/migrations/20230214065925_create_notification_types.exs index 1fcd57d48..4674aaa78 100644 --- a/priv/repo/migrations/20230214065925_create_notification_types.exs +++ b/priv/repo/migrations/20230214065925_create_notification_types.exs @@ -11,6 +11,6 @@ defmodule Cadet.Repo.Migrations.CreateNotificationTypes do timestamps() end - create unique_index(:notification_types, [:name]) + create(unique_index(:notification_types, [:name])) end end diff --git a/priv/repo/migrations/20230214140555_create_notification_preferences.exs b/priv/repo/migrations/20230214140555_create_notification_preferences.exs index bd7ffc26b..bce4849e6 100644 --- a/priv/repo/migrations/20230214140555_create_notification_preferences.exs +++ b/priv/repo/migrations/20230214140555_create_notification_preferences.exs @@ -5,7 +5,9 @@ defmodule Cadet.Repo.Migrations.CreateNotificationPreferences do create table(:notification_preferences) do add(:is_enabled, :boolean, default: false, null: false) - add(:notification_config_id, references(:notification_configs, on_delete: :delete_all, null: false), + add( + :notification_config_id, + references(:notification_configs, on_delete: :delete_all, null: false), null: false ) From 5b43002ebd71bc9003a046f4164a8940fea5bd13 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 28 Feb 2023 18:56:23 +0800 Subject: [PATCH 29/80] test: add tests for notification types --- lib/cadet/notifications/notification_type.ex | 2 +- .../notifications/notification_type_test.exs | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 test/cadet/notifications/notification_type_test.exs diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex index 377864314..3b52e25e1 100644 --- a/lib/cadet/notifications/notification_type.ex +++ b/lib/cadet/notifications/notification_type.ex @@ -20,7 +20,7 @@ defmodule Cadet.Notifications.NotificationType do def changeset(notification_type, attrs) do notification_type |> cast(attrs, [:name, :template_file_name, :is_enabled, :is_autopopulated]) - |> validate_required([:name, :template_file_name, :is_enabled, :is_autopopulated]) + |> validate_required([:name, :template_file_name, :is_autopopulated]) |> unique_constraint(:name) end end diff --git a/test/cadet/notifications/notification_type_test.exs b/test/cadet/notifications/notification_type_test.exs new file mode 100644 index 000000000..13170a631 --- /dev/null +++ b/test/cadet/notifications/notification_type_test.exs @@ -0,0 +1,68 @@ +defmodule Cadet.Notifications.NotificationTypeTest do + alias Cadet.Notifications.NotificationType + alias Cadet.Repo + + use Cadet.ChangesetCase, entity: NotificationType + + setup do + changeset = + NotificationType.changeset(%NotificationType{}, %{ + name: "Notification Type 1", + template_file_name: "template_file_1", + is_enabled: true, + is_autopopulated: true + }) + + {:ok, _noti_type1} = Repo.insert(changeset) + + {:ok, %{changeset: changeset}} + end + + describe "Changesets" do + test "valid changesets" do + assert_changeset( + %{ + name: "Notification Type 2", + template_file_name: "template_file_2", + is_enabled: false, + is_autopopulated: true + }, + :valid + ) + end + + test "invalid changesets missing name" do + assert_changeset( + %{ + template_file_name: "template_file_2", + is_enabled: false, + is_autopopulated: true + }, + :invalid + ) + end + + test "invalid changesets missing template_file_name" do + assert_changeset( + %{ + name: "Notification Type 2", + is_enabled: false, + is_autopopulated: true + }, + :invalid + ) + end + + test "invalid changeset duplicate name", %{changeset: changeset} do + {:error, changeset} = Repo.insert(changeset) + + assert changeset.errors == [ + name: + {"has already been taken", + [constraint: :unique, constraint_name: "notification_types_name_index"]} + ] + + refute changeset.valid? + end + end +end From b37f4a5c4f8d02391be3db467e35d3f7f268ef39 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 28 Feb 2023 19:16:38 +0800 Subject: [PATCH 30/80] test: add tests for time options --- lib/cadet/notifications/time_option.ex | 8 +- .../20230214132717_create_time_options.exs | 11 ++- test/cadet/notifications/time_option_test.exs | 98 +++++++++++++++++++ 3 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 test/cadet/notifications/time_option_test.exs diff --git a/lib/cadet/notifications/time_option.ex b/lib/cadet/notifications/time_option.ex index 39459ba92..a8047cfe3 100644 --- a/lib/cadet/notifications/time_option.ex +++ b/lib/cadet/notifications/time_option.ex @@ -8,7 +8,7 @@ defmodule Cadet.Notifications.TimeOption do schema "time_options" do field(:is_default, :boolean, default: false) - field(:minutes, :integer, default: nil) + field(:minutes, :integer) belongs_to(:notification_config, NotificationConfig) @@ -18,6 +18,10 @@ defmodule Cadet.Notifications.TimeOption do @doc false def changeset(time_option, attrs) do time_option - |> cast(attrs, [:minutes, :is_default]) + |> cast(attrs, [:minutes, :is_default, :notification_config_id]) + |> validate_required([:minutes, :notification_config_id]) + |> validate_number(:minutes, greater_than_or_equal_to: 0) + |> unique_constraint([:minutes, :notification_config_id], name: :unique_time_options) + |> foreign_key_constraint(:notification_config_id) end end diff --git a/priv/repo/migrations/20230214132717_create_time_options.exs b/priv/repo/migrations/20230214132717_create_time_options.exs index bd821b69e..911d56cd6 100644 --- a/priv/repo/migrations/20230214132717_create_time_options.exs +++ b/priv/repo/migrations/20230214132717_create_time_options.exs @@ -3,11 +3,18 @@ defmodule Cadet.Repo.Migrations.CreateTimeOptions do def change do create table(:time_options) do - add(:minutes, :integer) + add(:minutes, :integer, null: false) add(:is_default, :boolean, default: false, null: false) - add(:notification_config_id, references(:notification_configs, on_delete: :delete_all)) + + add(:notification_config_id, references(:notification_configs, on_delete: :delete_all), + null: false + ) timestamps() end + + create( + unique_index(:time_options, [:minutes, :notification_config_id], name: :unique_time_options) + ) end end diff --git a/test/cadet/notifications/time_option_test.exs b/test/cadet/notifications/time_option_test.exs new file mode 100644 index 000000000..ef281dcec --- /dev/null +++ b/test/cadet/notifications/time_option_test.exs @@ -0,0 +1,98 @@ +defmodule Cadet.Notifications.TimeOptionTest do + alias Cadet.Notifications.TimeOption + + use Cadet.ChangesetCase, entity: TimeOption + + setup do + course1 = insert(:course, %{course_short_name: "course 1"}) + + config1 = insert(:assessment_config, %{course: course1}) + + noti_type1 = insert(:notification_type, %{name: "Notification Type 1"}) + + noti_config1 = + insert(:notification_config, %{ + notification_type: noti_type1, + course: course1, + assessment_config: config1 + }) + + changeset = + TimeOption.changeset(%TimeOption{}, %{ + minutes: 10, + is_default: true, + notification_config_id: noti_config1.id + }) + + {:ok, _time_option1} = Repo.insert(changeset) + + {:ok, + %{ + noti_config1: noti_config1, + changeset: changeset + }} + end + + describe "Changesets" do + test "valid changesets", %{noti_config1: noti_config1} do + assert_changeset( + %{ + minutes: 20, + is_default: false, + notification_config_id: noti_config1.id + }, + :valid + ) + end + + test "invalid changesets missing minutes" do + assert_changeset( + %{ + is_default: false, + notification_config_id: 2 + }, + :invalid + ) + end + + test "invalid changesets missing notification_config_id" do + assert_changeset( + %{ + minutes: 2, + is_default: false + }, + :invalid + ) + end + + test "invalid changeset duplicate minutes", %{changeset: changeset} do + {:error, changeset} = Repo.insert(changeset) + + assert changeset.errors == [ + minutes: + {"has already been taken", + [constraint: :unique, constraint_name: "unique_time_options"]} + ] + end + + test "invalid notification_config_id", %{noti_config1: noti_config1} do + changeset = + TimeOption.changeset(%TimeOption{}, %{ + minutes: 10, + is_default: true, + notification_config_id: noti_config1.id + 1000 + }) + + {:error, changeset} = Repo.insert(changeset) + + assert changeset.errors == [ + notification_config_id: + {"does not exist", + [ + constraint: :foreign, + constraint_name: "time_options_notification_config_id_fkey" + ]} + ] + end + end +end From d6d6869bf4aad63a3a13d1d57c31b02b9ec89c01 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Wed, 1 Mar 2023 14:14:04 +0800 Subject: [PATCH 31/80] test: add tests for sent notifications --- lib/cadet/notifications/sent_notification.ex | 1 + ...230214143617_create_sent_notifications.exs | 2 +- .../notifications/sent_notification_test.exs | 88 +++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 test/cadet/notifications/sent_notification_test.exs diff --git a/lib/cadet/notifications/sent_notification.ex b/lib/cadet/notifications/sent_notification.ex index fb52b75af..5ff398624 100644 --- a/lib/cadet/notifications/sent_notification.ex +++ b/lib/cadet/notifications/sent_notification.ex @@ -19,5 +19,6 @@ defmodule Cadet.Notifications.SentNotification do sent_notification |> cast(attrs, [:content, :course_reg_id]) |> validate_required([:content, :course_reg_id]) + |> foreign_key_constraint(:course_reg_id) end end diff --git a/priv/repo/migrations/20230214143617_create_sent_notifications.exs b/priv/repo/migrations/20230214143617_create_sent_notifications.exs index b556f9246..fd0e1f428 100644 --- a/priv/repo/migrations/20230214143617_create_sent_notifications.exs +++ b/priv/repo/migrations/20230214143617_create_sent_notifications.exs @@ -3,7 +3,7 @@ defmodule Cadet.Repo.Migrations.CreateSentNotifications do def change do create table(:sent_notifications) do - add(:content, :text) + add(:content, :text, null: false) add(:course_reg_id, references(:course_registrations, on_delete: :nothing), null: false) timestamps() diff --git a/test/cadet/notifications/sent_notification_test.exs b/test/cadet/notifications/sent_notification_test.exs new file mode 100644 index 000000000..fa5e093a1 --- /dev/null +++ b/test/cadet/notifications/sent_notification_test.exs @@ -0,0 +1,88 @@ +defmodule Cadet.Notifications.SentNotificationTest do + alias Cadet.Notifications.SentNotification + alias Cadet.Repo + + use Cadet.ChangesetCase, entity: SentNotification + + setup do + course = insert(:course) + student = insert(:course_registration, %{course: course, role: :student}) + + changeset = + SentNotification.changeset(%SentNotification{}, %{ + content: "Test Content 1", + course_reg_id: student.id + }) + + {:ok, _sent_notification1} = Repo.insert(changeset) + + {:ok, + %{ + changeset: changeset, + course: course, + student: student + }} + end + + describe "Changesets" do + test "valid changesets", %{ + course: course, + student: student + } do + assert_changeset( + %{ + content: "Test Content 2", + course_reg_id: student.id + }, + :valid + ) + end + + test "invalid changesets missing content", %{ + course: course, + student: student + } do + assert_changeset( + %{ + course_reg_id: student.id + }, + :invalid + ) + end + + test "invalid changesets missing course_reg_id", %{ + course: course, + student: student + } do + assert_changeset( + %{ + content: "Test Content 2" + }, + :invalid + ) + end + + test "invalid changeset foreign key constraint", %{ + course: course, + student: student, + changeset: changeset + } do + changeset = + SentNotification.changeset(%SentNotification{}, %{ + content: "Test Content 2", + course_reg_id: student.id + 1000 + }) + + {:error, changeset} = Repo.insert(changeset) + + assert changeset.errors == [ + course_reg_id: + {"does not exist", + [ + constraint: :foreign, + constraint_name: "sent_notifications_course_reg_id_fkey" + ]} + ] + end + end +end From fdfd195010785a4ef9366185b712dc46103eaf64 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 1 Mar 2023 22:37:33 +0800 Subject: [PATCH 32/80] Add tests for notifications module --- lib/cadet/notifications.ex | 288 +------------ .../notifications/notification_config.ex | 7 + .../notifications/notification_preference.ex | 7 + lib/cadet/notifications/notification_type.ex | 7 + .../notifications/notifications_test.exs | 383 ++++++------------ test/factories/factory.ex | 1 + .../notification_preference_factory.ex | 20 + 7 files changed, 172 insertions(+), 541 deletions(-) create mode 100644 test/factories/notifications/notification_preference_factory.ex diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index ba89f24be..b675809bf 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -9,24 +9,11 @@ defmodule Cadet.Notifications do alias Cadet.Notifications.{ NotificationType, NotificationConfig, - SentNotification, + # SentNotification, TimeOption, NotificationPreference } - @doc """ - Returns the list of notification_types. - - ## Examples - - iex> list_notification_types() - [%NotificationType{}, ...] - - """ - def list_notification_types do - Repo.all(NotificationType) - end - @doc """ Gets a single notification_type. @@ -43,100 +30,6 @@ defmodule Cadet.Notifications do """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) - @doc """ - Creates a notification_type. - - ## Examples - - iex> create_notification_type(%{field: value}) - {:ok, %NotificationType{}} - - iex> create_notification_type(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_notification_type(attrs \\ %{}) do - %NotificationType{} - |> NotificationType.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a notification_type. - - ## Examples - - iex> update_notification_type(notification_type, %{field: new_value}) - {:ok, %NotificationType{}} - - iex> update_notification_type(notification_type, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_notification_type(notification_type = %NotificationType{}, attrs) do - notification_type - |> NotificationType.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a notification_type. - - ## Examples - - iex> delete_notification_type(notification_type) - {:ok, %NotificationType{}} - - iex> delete_notification_type(notification_type) - {:error, %Ecto.Changeset{}} - - """ - def delete_notification_type(notification_type = %NotificationType{}) do - Repo.delete(notification_type) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking notification_type changes. - - ## Examples - - iex> change_notification_type(notification_type) - %Ecto.Changeset{data: %NotificationType{}} - - """ - def change_notification_type(notification_type = %NotificationType{}, attrs \\ %{}) do - NotificationType.changeset(notification_type, attrs) - end - - @doc """ - Returns the list of notification_configs. - - ## Examples - - iex> list_notification_configs() - [%NotificationConfig{}, ...] - - """ - def list_notification_configs do - Repo.all(NotificationConfig) - end - - @doc """ - Gets a single notification_config. - - Raises `Ecto.NoResultsError` if the Notification config does not exist. - - ## Examples - - iex> get_notification_config!(123) - %NotificationConfig{} - - iex> get_notification_config!(456) - ** (Ecto.NoResultsError) - - """ - def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) - def get_notification_config!(notification_type_id, course_id, assconfig_id) do query = from(n in Cadet.Notifications.NotificationConfig, @@ -155,24 +48,6 @@ defmodule Cadet.Notifications do Repo.one!(query) end - @doc """ - Creates a notification_config. - - ## Examples - - iex> create_notification_config(%{field: value}) - {:ok, %NotificationConfig{}} - - iex> create_notification_config(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_notification_config(attrs \\ %{}) do - %NotificationConfig{} - |> NotificationConfig.changeset(attrs) - |> Repo.insert() - end - @doc """ Updates a notification_config. @@ -191,22 +66,6 @@ defmodule Cadet.Notifications do |> Repo.update() end - @doc """ - Deletes a notification_config. - - ## Examples - - iex> delete_notification_config(notification_config) - {:ok, %NotificationConfig{}} - - iex> delete_notification_config(notification_config) - {:error, %Ecto.Changeset{}} - - """ - def delete_notification_config(notification_config = %NotificationConfig{}) do - Repo.delete(notification_config) - end - @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_config changes. @@ -220,19 +79,6 @@ defmodule Cadet.Notifications do NotificationConfig.changeset(notification_config, attrs) end - @doc """ - Returns the list of time_options. - - ## Examples - - iex> list_time_options() - [%TimeOption{}, ...] - - """ - def list_time_options do - Repo.all(TimeOption) - end - @doc """ Gets a single time_option. @@ -297,24 +143,6 @@ defmodule Cadet.Notifications do |> Repo.insert() end - @doc """ - Updates a time_option. - - ## Examples - - iex> update_time_option(time_option, %{field: new_value}) - {:ok, %TimeOption{}} - - iex> update_time_option(time_option, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_time_option(time_option = %TimeOption{}, attrs) do - time_option - |> TimeOption.changeset(attrs) - |> Repo.update() - end - @doc """ Deletes a time_option. @@ -331,48 +159,6 @@ defmodule Cadet.Notifications do Repo.delete(time_option) end - @doc """ - Returns an `%Ecto.Changeset{}` for tracking time_option changes. - - ## Examples - - iex> change_time_option(time_option) - %Ecto.Changeset{data: %TimeOption{}} - - """ - def change_time_option(time_option = %TimeOption{}, attrs \\ %{}) do - TimeOption.changeset(time_option, attrs) - end - - @doc """ - Returns the list of notification_preferences. - - ## Examples - - iex> list_notification_preferences() - [%NotificationPreference{}, ...] - - """ - def list_notification_preferences do - Repo.all(NotificationPreference) - end - - @doc """ - Gets a single notification_preference. - - Raises `Ecto.NoResultsError` if the Notification preference does not exist. - - ## Examples - - iex> get_notification_preference!(123) - %NotificationPreference{} - - iex> get_notification_preference!(456) - ** (Ecto.NoResultsError) - - """ - def get_notification_preference!(id), do: Repo.get!(NotificationPreference, id) - def get_notification_preference(notification_type_id, course_reg_id) do query = from(np in NotificationPreference, @@ -464,9 +250,9 @@ defmodule Cadet.Notifications do [%SentNotification{}, ...] """ - def list_sent_notifications do - Repo.all(SentNotification) - end + # def list_sent_notifications do + # Repo.all(SentNotification) + # end @doc """ Gets a single sent_notification. @@ -482,70 +268,6 @@ defmodule Cadet.Notifications do ** (Ecto.NoResultsError) """ - def get_sent_notification!(id), do: Repo.get!(SentNotification, id) - - @doc """ - Creates a sent_notification. - - ## Examples - - iex> create_sent_notification(%{field: value}) - {:ok, %SentNotification{}} - - iex> create_sent_notification(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_sent_notification(attrs \\ %{}) do - %SentNotification{} - |> SentNotification.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a sent_notification. - - ## Examples - - iex> update_sent_notification(sent_notification, %{field: new_value}) - {:ok, %SentNotification{}} - - iex> update_sent_notification(sent_notification, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_sent_notification(sent_notification = %SentNotification{}, attrs) do - sent_notification - |> SentNotification.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a sent_notification. - - ## Examples - - iex> delete_sent_notification(sent_notification) - {:ok, %SentNotification{}} - - iex> delete_sent_notification(sent_notification) - {:error, %Ecto.Changeset{}} - - """ - def delete_sent_notification(sent_notification = %SentNotification{}) do - Repo.delete(sent_notification) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking sent_notification changes. + # def get_sent_notification!(id), do: Repo.get!(SentNotification, id) - ## Examples - - iex> change_sent_notification(sent_notification) - %Ecto.Changeset{data: %SentNotification{}} - - """ - def change_sent_notification(sent_notification = %SentNotification{}, attrs \\ %{}) do - SentNotification.changeset(sent_notification, attrs) - end end diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex index c9e6505e6..5f3a18751 100644 --- a/lib/cadet/notifications/notification_config.ex +++ b/lib/cadet/notifications/notification_config.ex @@ -22,5 +22,12 @@ defmodule Cadet.Notifications.NotificationConfig do notification_config |> cast(attrs, [:is_enabled, :notification_type_id, :course_id]) |> validate_required([:notification_type_id, :course_id]) + |> prevent_nil_is_enabled() end + + defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) when is_nil(is_enabled), + do: add_error(changeset, :full_name, "empty") + + defp prevent_nil_is_enabled(changeset), + do: changeset end diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index e98e6e034..d5daaefa5 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -22,5 +22,12 @@ defmodule Cadet.Notifications.NotificationPreference do notification_preference |> cast(attrs, [:is_enabled, :notification_config_id, :course_reg_id]) |> validate_required([:notification_config_id, :course_reg_id]) + |> prevent_nil_is_enabled() end + + defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) when is_nil(is_enabled), + do: add_error(changeset, :full_name, "empty") + + defp prevent_nil_is_enabled(changeset), + do: changeset end diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex index 3b52e25e1..721af4d7d 100644 --- a/lib/cadet/notifications/notification_type.ex +++ b/lib/cadet/notifications/notification_type.ex @@ -22,5 +22,12 @@ defmodule Cadet.Notifications.NotificationType do |> cast(attrs, [:name, :template_file_name, :is_enabled, :is_autopopulated]) |> validate_required([:name, :template_file_name, :is_autopopulated]) |> unique_constraint(:name) + |> prevent_nil_is_enabled() end + + defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) when is_nil(is_enabled), + do: add_error(changeset, :full_name, "empty") + + defp prevent_nil_is_enabled(changeset), + do: changeset end diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 002b6bf1e..43a487f05 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -4,254 +4,162 @@ defmodule Cadet.NotificationsTest do alias Cadet.Notifications describe "notification_types" do - # alias Cadet.Notifications.NotificationType - - # import Cadet.NotificationsFixtures - - # @invalid_attrs %{is_autopopulated: nil, is_enabled: nil, name: nil, template_file_name: nil} - - # test "list_notification_types/0 returns all notification_types" do - # notification_type = notification_type_fixture() - # assert Notifications.list_notification_types() == [notification_type] - # end - - # test "get_notification_type!/1 returns the notification_type with given id" do - # notification_type = notification_type_fixture() - # assert Notifications.get_notification_type!(notification_type.id) == notification_type - # end - - # test "create_notification_type/1 with valid data creates a notification_type" do - # valid_attrs = %{ - # is_autopopulated: true, - # is_enabled: true, - # name: "some name", - # template_file_name: "some template_file_name" - # } - - # assert {:ok, %NotificationType{} = notification_type} = - # Notifications.create_notification_type(valid_attrs) - # assert notification_type.is_autopopulated == true - # assert notification_type.is_enabled == true - # assert notification_type.name == "some name" - # assert notification_type.template_file_name == "some template_file_name" - # end - - # test "create_notification_type/1 with invalid data returns error changeset" do - # assert {:error, %Ecto.Changeset{}} = Notifications.create_notification_type(@invalid_attrs) - # end - - # test "update_notification_type/2 with valid data updates the notification_type" do - # notification_type = notification_type_fixture() - # update_attrs = %{ - # is_autopopulated: false, - # is_enabled: false, - # name: "some updated name", - # template_file_name: "some updated template_file_name" - # } - - # assert {:ok, %NotificationType{} = notification_type} = - # Notifications.update_notification_type(notification_type, update_attrs) - # assert notification_type.is_autopopulated == false - # assert notification_type.is_enabled == false - # assert notification_type.name == "some updated name" - # assert notification_type.template_file_name == "some updated template_file_name" - # end - - # test "update_notification_type/2 with invalid data returns error changeset" do - # notification_type = notification_type_fixture() - # assert {:error, %Ecto.Changeset{}} = - # Notifications.update_notification_type(notification_type, @invalid_attrs) - # assert notification_type == Notifications.get_notification_type!(notification_type.id) - # end - - # test "delete_notification_type/1 deletes the notification_type" do - # notification_type = notification_type_fixture() - # assert {:ok, %NotificationType{}} = - # Notifications.delete_notification_type(notification_type) - # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_type!(notification_type.id) end - # end - - # test "change_notification_type/1 returns a notification_type changeset" do - # notification_type = notification_type_fixture() - # assert %Ecto.Changeset{} = Notifications.change_notification_type(notification_type) - # end + test "get_notification_type!/1 returns the notification_type with given id" do + ntype = insert(:notification_type) + result = Notifications.get_notification_type!(ntype.id) + assert ntype.id == result.id + end end describe "notification_configs" do - # alias Cadet.Notifications.NotificationConfig + alias Cadet.Notifications.NotificationConfig - # import Cadet.NotificationsFixtures + @invalid_attrs %{is_enabled: nil} - # @invalid_attrs %{is_enabled: nil} + test "get_notification_config!/3 returns the notification_config with given id" do + notification_config = insert(:notification_config) - # test "list_notification_configs/0 returns all notification_configs" do - # notification_config = notification_config_fixture() - # assert Notifications.list_notification_configs() == [notification_config] - # end + assert Notifications.get_notification_config!( + notification_config.notification_type.id, + notification_config.course.id, + notification_config.assessment_config.id + ).id == notification_config.id + end - # test "get_notification_config!/1 returns the notification_config with given id" do - # notification_config = notification_config_fixture() - # assert Notifications.get_notification_config!(notification_config.id) == notification_config - # end + test "update_notification_config/2 with valid data updates the notification_config" do + notification_config = insert(:notification_config) + update_attrs = %{is_enabled: true} - # test "create_notification_config/1 with valid data creates a notification_config" do - # valid_attrs = %{is_enabled: true} + assert {:ok, %NotificationConfig{} = notification_config} = + Notifications.update_notification_config(notification_config, update_attrs) - # assert {:ok, %NotificationConfig{} = notification_config} = - # Notifications.create_notification_config(valid_attrs) - # assert notification_config.is_enabled == true - # end + assert notification_config.is_enabled == true + end - # test "create_notification_config/1 with invalid data returns error changeset" do - # assert {:error, %Ecto.Changeset{}} = - # Notifications.create_notification_config(@invalid_attrs) - # end - - # test "update_notification_config/2 with valid data updates the notification_config" do - # notification_config = notification_config_fixture() - # update_attrs = %{is_enabled: false} + test "update_notification_config/2 with invalid data returns error changeset" do + notification_config = insert(:notification_config) - # assert {:ok, %NotificationConfig{} = notification_config} = - # Notifications.update_notification_config(notification_config, update_attrs) - # assert notification_config.is_enabled == false - # end + assert {:error, %Ecto.Changeset{}} = + Notifications.update_notification_config(notification_config, @invalid_attrs) - # test "update_notification_config/2 with invalid data returns error changeset" do - # notification_config = notification_config_fixture() - # assert {:error, %Ecto.Changeset{}} = - # Notifications.update_notification_config(notification_config, @invalid_attrs) - # assert notification_config == Notifications.get_notification_config!(notification_config.id) - # end + assert notification_config.id == + Notifications.get_notification_config!( + notification_config.notification_type.id, + notification_config.course.id, + notification_config.assessment_config.id + ).id + end - # test "delete_notification_config/1 deletes the notification_config" do - # notification_config = notification_config_fixture() - # assert {:ok, %NotificationConfig{}} = - # Notifications.delete_notification_config(notification_config) - # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_config!(notification_config.id) end - # end - - # test "change_notification_config/1 returns a notification_config changeset" do - # notification_config = notification_config_fixture() - # assert %Ecto.Changeset{} = Notifications.change_notification_config(notification_config) - # end + test "change_notification_config/1 returns a notification_config changeset" do + notification_config = insert(:notification_config) + assert %Ecto.Changeset{} = Notifications.change_notification_config(notification_config) + end end describe "time_options" do - # alias Cadet.Notifications.TimeOption - - # import Cadet.NotificationsFixtures - - # @invalid_attrs %{is_default: nil, minutes: nil} - - # test "list_time_options/0 returns all time_options" do - # time_option = time_option_fixture() - # assert Notifications.list_time_options() == [time_option] - # end - - # test "get_time_option!/1 returns the time_option with given id" do - # time_option = time_option_fixture() - # assert Notifications.get_time_option!(time_option.id) == time_option - # end - - # test "create_time_option/1 with valid data creates a time_option" do - # valid_attrs = %{is_default: true, minutes: 42} - - # assert {:ok, %TimeOption{} = time_option} = Notifications.create_time_option(valid_attrs) - # assert time_option.is_default == true - # assert time_option.minutes == 42 - # end - - # test "create_time_option/1 with invalid data returns error changeset" do - # assert {:error, %Ecto.Changeset{}} = Notifications.create_time_option(@invalid_attrs) - # end - - # test "update_time_option/2 with valid data updates the time_option" do - # time_option = time_option_fixture() - # update_attrs = %{is_default: false, minutes: 43} - - # assert {:ok, %TimeOption{} = time_option} = - # Notifications.update_time_option(time_option, update_attrs) - # assert time_option.is_default == false - # assert time_option.minutes == 43 - # end - - # test "update_time_option/2 with invalid data returns error changeset" do - # time_option = time_option_fixture() - # assert {:error, %Ecto.Changeset{}} = - # Notifications.update_time_option(time_option, @invalid_attrs) - # assert time_option == Notifications.get_time_option!(time_option.id) - # end - - # test "delete_time_option/1 deletes the time_option" do - # time_option = time_option_fixture() - # assert {:ok, %TimeOption{}} = Notifications.delete_time_option(time_option) - # assert_raise Ecto.NoResultsError, fn -> Notifications.get_time_option!(time_option.id) end - # end - - # test "change_time_option/1 returns a time_option changeset" do - # time_option = time_option_fixture() - # assert %Ecto.Changeset{} = Notifications.change_time_option(time_option) - # end + alias Cadet.Notifications.TimeOption + + @invalid_attrs %{is_default: nil, minutes: nil} + + test "get_time_option!/1 returns the time_option with given id" do + time_option = insert(:time_option) + assert Notifications.get_time_option!(time_option.id).id == time_option.id + end + + test "get_time_options_for_assessment/2 returns the time_option with given ids" do + time_option = insert(:time_option) + + assert List.first( + Notifications.get_time_options_for_assessment( + time_option.notification_config.assessment_config.id, + time_option.notification_config.notification_type.id + ) + ).id == time_option.id + end + + test "get_default_time_option_for_assessment!/2 returns the time_option with given ids" do + time_option = insert(:time_option, is_default: true) + + assert Notifications.get_default_time_option_for_assessment!( + time_option.notification_config.assessment_config.id, + time_option.notification_config.notification_type.id + ).id == time_option.id + end + + test "create_time_option/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Notifications.create_time_option(@invalid_attrs) + end + + test "delete_time_option/1 deletes the time_option" do + time_option = insert(:time_option) + assert {:ok, %TimeOption{}} = Notifications.delete_time_option(time_option) + assert_raise Ecto.NoResultsError, fn -> Notifications.get_time_option!(time_option.id) end + end end describe "notification_preferences" do - # alias Cadet.Notifications.NotificationPreference + alias Cadet.Notifications.NotificationPreference - # import Cadet.NotificationsFixtures + @invalid_attrs %{is_enabled: nil} - # @invalid_attrs %{is_enable: nil} + test "get_notification_preference!/1 returns the notification_preference with given id" do + notification_preference = insert(:notification_preference) - # test "list_notification_preferences/0 returns all notification_preferences" do - # notification_preference = notification_preference_fixture() - # assert Notifications.list_notification_preferences() == [notification_preference] - # end + assert Notifications.get_notification_preference( + notification_preference.notification_config.notification_type.id, + notification_preference.course_reg.id + ).id == notification_preference.id + end - # test "get_notification_preference!/1 returns the notification_preference with given id" do - # notification_preference = notification_preference_fixture() - # assert Notifications.get_notification_preference!(notification_preference.id) == notification_preference - # end + test "create_notification_preference/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = + Notifications.create_notification_preference(@invalid_attrs) + end - # test "create_notification_preference/1 with valid data creates a notification_preference" do - # valid_attrs = %{is_enabled: true} + test "update_notification_preference/2 with valid data updates the notification_preference" do + notification_preference = insert(:notification_preference) + update_attrs = %{is_enabled: true} - # assert {:ok, %NotificationPreference{} = notification_preference} = - # Notifications.create_notification_preference(valid_attrs) - # assert notification_preference.is_enabled == true - # end + assert {:ok, %NotificationPreference{} = notification_preference} = + Notifications.update_notification_preference(notification_preference, update_attrs) - # test "create_notification_preference/1 with invalid data returns error changeset" do - # assert {:error, %Ecto.Changeset{}} = - # Notifications.create_notification_preference(@invalid_attrs) - # end + assert notification_preference.is_enabled == true + end - # test "update_notification_preference/2 with valid data updates the notification_preference" do - # notification_preference = notification_preference_fixture() - # update_attrs = %{is_enabled: false} + test "update_notification_preference/2 with invalid data returns error changeset" do + notification_preference = insert(:notification_preference) - # assert {:ok, %NotificationPreference{} = notification_preference} = - # Notifications.update_notification_preference(notification_preference, update_attrs) - # assert notification_preference.is_enabled == false - # end + assert {:error, %Ecto.Changeset{}} = + Notifications.update_notification_preference( + notification_preference, + @invalid_attrs + ) - # test "update_notification_preference/2 with invalid data returns error changeset" do - # notification_preference = notification_preference_fixture() - # assert {:error, %Ecto.Changeset{}} = - # Notifications.update_notification_preference(notification_preference, @invalid_attrs) - # assert notification_preference == Notifications.get_notification_preference!(notification_preference.id) - # end + assert notification_preference.id == + Notifications.get_notification_preference( + notification_preference.notification_config.notification_type.id, + notification_preference.course_reg.id + ).id + end - # test "delete_notification_preference/1 deletes the notification_preference" do - # notification_preference = notification_preference_fixture() - # assert {:ok, %NotificationPreference{}} = - # Notifications.delete_notification_preference(notification_preference) - # assert_raise Ecto.NoResultsError, fn -> Notifications.get_notification_preference!(notification_preference.id) end - # end + test "delete_notification_preference/1 deletes the notification_preference" do + notification_preference = insert(:notification_preference) - # test "change_notification_preference/1 returns a notification_preference changeset" do - # notification_preference = notification_preference_fixture() - # assert %Ecto.Changeset{} = - # Notifications.change_notification_preference(notification_preference) - # end + assert {:ok, %NotificationPreference{}} = + Notifications.delete_notification_preference(notification_preference) + + assert Notifications.get_notification_preference( + notification_preference.notification_config.notification_type.id, + notification_preference.course_reg.id + ) == nil + end + + test "change_notification_preference/1 returns a notification_preference changeset" do + notification_preference = insert(:notification_preference) + + assert %Ecto.Changeset{} = + Notifications.change_notification_preference(notification_preference) + end end describe "sent_notifications" do @@ -270,46 +178,5 @@ defmodule Cadet.NotificationsTest do # sent_notification = sent_notification_fixture() # assert Notifications.get_sent_notification!(sent_notification.id) == sent_notification # end - - # test "create_sent_notification/1 with valid data creates a sent_notification" do - # valid_attrs = %{content: "some content"} - - # assert {:ok, %SentNotification{} = sent_notification} = - # Notifications.create_sent_notification(valid_attrs) - # assert sent_notification.content == "some content" - # end - - # test "create_sent_notification/1 with invalid data returns error changeset" do - # assert {:error, %Ecto.Changeset{}} = Notifications.create_sent_notification(@invalid_attrs) - # end - - # test "update_sent_notification/2 with valid data updates the sent_notification" do - # sent_notification = sent_notification_fixture() - # update_attrs = %{content: "some updated content"} - - # assert {:ok, %SentNotification{} = sent_notification} = - # Notifications.update_sent_notification(sent_notification, update_attrs) - # assert sent_notification.content == "some updated content" - # end - - # test "update_sent_notification/2 with invalid data returns error changeset" do - # sent_notification = sent_notification_fixture() - # assert {:error, %Ecto.Changeset{}} = - # Notifications.update_sent_notification(sent_notification, @invalid_attrs) - # assert sent_notification == Notifications.get_sent_notification!(sent_notification.id) - # end - - # test "delete_sent_notification/1 deletes the sent_notification" do - # sent_notification = sent_notification_fixture() - # assert {:ok, %SentNotification{}} = - # Notifications.delete_sent_notification(sent_notification) - # assert_raise Ecto.NoResultsError, fn -> Notifications.get_sent_notification!(sent_notification.id) end - # end - - # test "change_sent_notification/1 returns a sent_notification changeset" do - # sent_notification = sent_notification_fixture() - # assert %Ecto.Changeset{} = - # Notifications.change_sent_notification(sent_notification) - # end end end diff --git a/test/factories/factory.ex b/test/factories/factory.ex index fb3a28cd0..469346ff5 100644 --- a/test/factories/factory.ex +++ b/test/factories/factory.ex @@ -32,6 +32,7 @@ defmodule Cadet.Factory do use Cadet.Notifications.{ NotificationTypeFactory, NotificationConfigFactory, + NotificationPreferenceFactory, TimeOptionFactory } diff --git a/test/factories/notifications/notification_preference_factory.ex b/test/factories/notifications/notification_preference_factory.ex new file mode 100644 index 000000000..49ffb90bd --- /dev/null +++ b/test/factories/notifications/notification_preference_factory.ex @@ -0,0 +1,20 @@ +defmodule Cadet.Notifications.NotificationPreferenceFactory do + @moduledoc """ + Factory for the NotificationPreference entity + """ + + defmacro __using__(_opts) do + quote do + alias Cadet.Notifications.NotificationPreference + + def notification_preference_factory do + %NotificationPreference{ + is_enabled: false, + notification_config: build(:notification_config), + time_option: build(:time_option), + course_reg: build(:course_registration) + } + end + end + end +end From 498ca1b34c0d1d807cebbbaccb183584d0884f9e Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Thu, 2 Mar 2023 17:22:32 +0800 Subject: [PATCH 33/80] fix: Fix testing with Oban Oban introduced changes to testing in v2.12, this commit changes the old test configurations to the new one recommended by official docs. --- config/test.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/test.exs b/config/test.exs index 5d3245a27..05ca74630 100644 --- a/config/test.exs +++ b/config/test.exs @@ -91,7 +91,6 @@ if "test.secrets.exs" |> Path.expand(__DIR__) |> File.exists?(), config :cadet, Oban, repo: Cadet.Repo, - plugins: false, - queues: false + testing: :manual config :cadet, Cadet.Mailer, adapter: Bamboo.TestAdapter From 70823a6ef1892ad80475b39cbc9835a9d961c238 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:12:02 +0800 Subject: [PATCH 34/80] Add more tests for notifications module --- test/cadet/notifications/notification_type_test.exs | 11 +++++++++++ test/cadet/notifications/notifications_test.exs | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/test/cadet/notifications/notification_type_test.exs b/test/cadet/notifications/notification_type_test.exs index 13170a631..0c98b0b73 100644 --- a/test/cadet/notifications/notification_type_test.exs +++ b/test/cadet/notifications/notification_type_test.exs @@ -64,5 +64,16 @@ defmodule Cadet.Notifications.NotificationTypeTest do refute changeset.valid? end + + test "invalid changeset nil is_enabled", %{changeset: changeset} do + assert_changeset( + %{ + name: "Notification Type 0", + is_enabled: nil, + is_autopopulated: true + }, + :invalid + ) + end end end diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 43a487f05..9e009b6bc 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -26,6 +26,16 @@ defmodule Cadet.NotificationsTest do ).id == notification_config.id end + test "get_notification_config!/3 with no assessment config returns the notification_config with given id" do + notification_config = insert(:notification_config, assessment_config: nil) + + assert Notifications.get_notification_config!( + notification_config.notification_type.id, + notification_config.course.id, + nil + ).id == notification_config.id + end + test "update_notification_config/2 with valid data updates the notification_config" do notification_config = insert(:notification_config) update_attrs = %{is_enabled: true} From cf160f8b845063cecd1a31bb0f4b3dc55ec47c10 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Sun, 5 Mar 2023 22:56:32 +0800 Subject: [PATCH 35/80] test: add tests for NotificationWorker --- lib/cadet/notifications.ex | 19 +++++++- lib/cadet/workers/NotificationWorker.ex | 9 ++-- .../notification_worker_test.exs | 44 +++++++++++++++++++ test/support/seeds.ex | 8 +++- 4 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 test/cadet/jobs/notification_worker/notification_worker_test.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index b675809bf..7c84600c0 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -30,6 +30,23 @@ defmodule Cadet.Notifications do """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) + @doc """ + Gets a single notification_type by name.any() + + Raises `Ecto.NoResultsError` if the Notification type does not exist. + + ## Examples + + iex> get_notification_type_by_name!("AVENGER BACKLOG") + %NotificationType{} + + iex> get_notification_type_by_name!("AVENGER BACKLOG") + ** (Ecto.NoResultsError) + """ + def get_notification_type_by_name!(name) do + Repo.one!(from(nt in NotificationType, where: nt.name == ^name)) + end + def get_notification_config!(notification_type_id, course_id, assconfig_id) do query = from(n in Cadet.Notifications.NotificationConfig, @@ -250,6 +267,7 @@ defmodule Cadet.Notifications do [%SentNotification{}, ...] """ + # def list_sent_notifications do # Repo.all(SentNotification) # end @@ -269,5 +287,4 @@ defmodule Cadet.Notifications do """ # def get_sent_notification!(id), do: Repo.get!(SentNotification, id) - end diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index 540dd694e..22bf51c79 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -52,10 +52,10 @@ defmodule Cadet.Workers.NotificationWorker do args: %{"notification_type" => notification_type} = _args }) when notification_type == "avenger_backlog" do - notification_type_id = 2 ungraded_threshold = 5 - ntype = Cadet.Notifications.get_notification_type!(notification_type_id) + ntype = Cadet.Notifications.get_notification_type_by_name!("AVENGER BACKLOG") + notification_type_id = ntype.id for course_id <- Cadet.Courses.get_all_course_ids() do avengers_crs = Cadet.Accounts.CourseRegistrations.get_staffs(course_id) @@ -65,7 +65,10 @@ defmodule Cadet.Workers.NotificationWorker do ungraded_submissions = Jason.decode!( - elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), 1) + elem( + Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), + 1 + ) ) cond do diff --git a/test/cadet/jobs/notification_worker/notification_worker_test.exs b/test/cadet/jobs/notification_worker/notification_worker_test.exs new file mode 100644 index 000000000..ba3e8fe83 --- /dev/null +++ b/test/cadet/jobs/notification_worker/notification_worker_test.exs @@ -0,0 +1,44 @@ +defmodule Cadet.NotificationWorker.NotificationWorkerTest do + use ExUnit.Case, async: true + use Oban.Testing, repo: Cadet.Repo + use Cadet.DataCase + use Bamboo.Test + + alias Cadet.Repo + alias Cadet.Workers.NotificationWorker + alias Cadet.Notifications.{NotificationType, NotificationConfig} + + setup do + assessments = Cadet.Test.Seeds.assessments() + avenger_cr = assessments.course_regs.avenger1_cr + + ungraded_submissions = + Jason.decode!( + elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), 1) + ) + + Repo.update_all(NotificationType, set: [is_enabled: true]) + Repo.update_all(NotificationConfig, set: [is_enabled: true]) + + {:ok, %{avenger_user: avenger_cr.user, ungraded_submissions: ungraded_submissions}} + end + + test "avenger backlog test", %{ + avenger_user: avenger_user + } do + perform_job(NotificationWorker, %{"notification_type" => "avenger_backlog"}) + # ntype = Cadet.Notifications.get_notification_type!(1) + + # email = + # Cadet.Email.avenger_backlog_email( + # ntype.template_file_name, + # avenger_user, + # ungraded_submissions + # ) + + # assert_delivered_email(email) + avenger_email = avenger_user.email + + assert_delivered_email_matches(%{to: [{_, ^avenger_email}]}) + end +end diff --git a/test/support/seeds.ex b/test/support/seeds.ex index ef05b1bd5..e88ec7f21 100644 --- a/test/support/seeds.ex +++ b/test/support/seeds.ex @@ -41,7 +41,13 @@ defmodule Cadet.Test.Seeds do course1 = insert(:course) course2 = insert(:course, %{course_name: "Algorithm", course_short_name: "CS2040S"}) # Users - avenger1 = insert(:user, %{name: "avenger", latest_viewed_course: course1}) + avenger1 = + insert(:user, %{ + name: "avenger", + latest_viewed_course: course1, + email: "avenger1@gmail.com" + }) + admin1 = insert(:user, %{name: "admin", latest_viewed_course: course1}) studenta1admin2 = insert(:user, %{name: "student a", latest_viewed_course: course1}) From 83d49435fb4d52bcc4d3ff064ebf51cfd3986d5a Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Sun, 5 Mar 2023 22:56:56 +0800 Subject: [PATCH 36/80] style: fix formatting --- test/cadet/notifications/sent_notification_test.exs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/cadet/notifications/sent_notification_test.exs b/test/cadet/notifications/sent_notification_test.exs index fa5e093a1..4bfb51b7d 100644 --- a/test/cadet/notifications/sent_notification_test.exs +++ b/test/cadet/notifications/sent_notification_test.exs @@ -26,7 +26,6 @@ defmodule Cadet.Notifications.SentNotificationTest do describe "Changesets" do test "valid changesets", %{ - course: course, student: student } do assert_changeset( @@ -39,7 +38,6 @@ defmodule Cadet.Notifications.SentNotificationTest do end test "invalid changesets missing content", %{ - course: course, student: student } do assert_changeset( @@ -50,10 +48,7 @@ defmodule Cadet.Notifications.SentNotificationTest do ) end - test "invalid changesets missing course_reg_id", %{ - course: course, - student: student - } do + test "invalid changesets missing course_reg_id" do assert_changeset( %{ content: "Test Content 2" @@ -63,9 +58,7 @@ defmodule Cadet.Notifications.SentNotificationTest do end test "invalid changeset foreign key constraint", %{ - course: course, - student: student, - changeset: changeset + student: student } do changeset = SentNotification.changeset(%SentNotification{}, %{ From bf362c7159579c143567b39325bf04587dabda21 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Sun, 5 Mar 2023 23:12:20 +0800 Subject: [PATCH 37/80] style: fix formatting --- lib/cadet/notifications/notification_config.ex | 5 +++-- lib/cadet/notifications/notification_preference.ex | 5 +++-- lib/cadet/notifications/notification_type.ex | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex index 5f3a18751..2072b9f45 100644 --- a/lib/cadet/notifications/notification_config.ex +++ b/lib/cadet/notifications/notification_config.ex @@ -25,8 +25,9 @@ defmodule Cadet.Notifications.NotificationConfig do |> prevent_nil_is_enabled() end - defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) when is_nil(is_enabled), - do: add_error(changeset, :full_name, "empty") + defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) + when is_nil(is_enabled), + do: add_error(changeset, :full_name, "empty") defp prevent_nil_is_enabled(changeset), do: changeset diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index d5daaefa5..aec18aa5e 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -25,8 +25,9 @@ defmodule Cadet.Notifications.NotificationPreference do |> prevent_nil_is_enabled() end - defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) when is_nil(is_enabled), - do: add_error(changeset, :full_name, "empty") + defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) + when is_nil(is_enabled), + do: add_error(changeset, :full_name, "empty") defp prevent_nil_is_enabled(changeset), do: changeset diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex index 721af4d7d..7f16df022 100644 --- a/lib/cadet/notifications/notification_type.ex +++ b/lib/cadet/notifications/notification_type.ex @@ -25,8 +25,9 @@ defmodule Cadet.Notifications.NotificationType do |> prevent_nil_is_enabled() end - defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) when is_nil(is_enabled), - do: add_error(changeset, :full_name, "empty") + defp prevent_nil_is_enabled(changeset = %{changes: %{is_enabled: is_enabled}}) + when is_nil(is_enabled), + do: add_error(changeset, :full_name, "empty") defp prevent_nil_is_enabled(changeset), do: changeset From 05aac5c83f35600cae0fa0050f8cb542abcb3e07 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Mon, 6 Mar 2023 22:51:22 +0800 Subject: [PATCH 38/80] style: fix formatting --- test/cadet/notifications/notification_type_test.exs | 2 +- test/cadet/notifications/notifications_test.exs | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/test/cadet/notifications/notification_type_test.exs b/test/cadet/notifications/notification_type_test.exs index 0c98b0b73..547795521 100644 --- a/test/cadet/notifications/notification_type_test.exs +++ b/test/cadet/notifications/notification_type_test.exs @@ -65,7 +65,7 @@ defmodule Cadet.Notifications.NotificationTypeTest do refute changeset.valid? end - test "invalid changeset nil is_enabled", %{changeset: changeset} do + test "invalid changeset nil is_enabled" do assert_changeset( %{ name: "Notification Type 0", diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index d8d1c16b8..ef32afd34 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -2,6 +2,7 @@ defmodule Cadet.NotificationsTest do use Cadet.DataCase alias Cadet.Notifications + alias Cadet.Notifications.{NotificationConfig, NotificationPreference, TimeOption} describe "notification_types" do test "get_notification_type!/1 returns the notification_type with given id" do @@ -12,8 +13,6 @@ defmodule Cadet.NotificationsTest do end describe "notification_configs" do - alias Cadet.Notifications.NotificationConfig - @invalid_attrs %{is_enabled: nil} test "get_notification_config!/3 returns the notification_config with given id" do @@ -67,8 +66,6 @@ defmodule Cadet.NotificationsTest do end describe "time_options" do - alias Cadet.Notifications.TimeOption - @invalid_attrs %{is_default: nil, minutes: nil} test "get_time_option!/1 returns the time_option with given id" do @@ -108,8 +105,6 @@ defmodule Cadet.NotificationsTest do end describe "notification_preferences" do - alias Cadet.Notifications.NotificationPreference - @invalid_attrs %{is_enabled: nil} test "get_notification_preference!/1 returns the notification_preference with given id" do From 8f0e9821089cac9329736efc2851c30bb8ed5f88 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Tue, 7 Mar 2023 02:37:02 +0800 Subject: [PATCH 39/80] fix: Fix tests under notification types name constraints --- .../notifications/notifications_test.exs | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 9e009b6bc..d8d1c16b8 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -113,7 +113,11 @@ defmodule Cadet.NotificationsTest do @invalid_attrs %{is_enabled: nil} test "get_notification_preference!/1 returns the notification_preference with given id" do - notification_preference = insert(:notification_preference) + notification_type = insert(:notification_type, name: "get_notification_preference!/1") + notification_config = insert(:notification_config, notification_type: notification_type) + + notification_preference = + insert(:notification_preference, notification_config: notification_config) assert Notifications.get_notification_preference( notification_preference.notification_config.notification_type.id, @@ -127,7 +131,14 @@ defmodule Cadet.NotificationsTest do end test "update_notification_preference/2 with valid data updates the notification_preference" do - notification_preference = insert(:notification_preference) + notification_type = + insert(:notification_type, name: "update_notification_preference/2 valid") + + notification_config = insert(:notification_config, notification_type: notification_type) + + notification_preference = + insert(:notification_preference, notification_config: notification_config) + update_attrs = %{is_enabled: true} assert {:ok, %NotificationPreference{} = notification_preference} = @@ -137,7 +148,13 @@ defmodule Cadet.NotificationsTest do end test "update_notification_preference/2 with invalid data returns error changeset" do - notification_preference = insert(:notification_preference) + notification_type = + insert(:notification_type, name: "update_notification_preference/2 invalid") + + notification_config = insert(:notification_config, notification_type: notification_type) + + notification_preference = + insert(:notification_preference, notification_config: notification_config) assert {:error, %Ecto.Changeset{}} = Notifications.update_notification_preference( @@ -153,7 +170,11 @@ defmodule Cadet.NotificationsTest do end test "delete_notification_preference/1 deletes the notification_preference" do - notification_preference = insert(:notification_preference) + notification_type = insert(:notification_type, name: "delete_notification_preference/1") + notification_config = insert(:notification_config, notification_type: notification_type) + + notification_preference = + insert(:notification_preference, notification_config: notification_config) assert {:ok, %NotificationPreference{}} = Notifications.delete_notification_preference(notification_preference) @@ -165,7 +186,11 @@ defmodule Cadet.NotificationsTest do end test "change_notification_preference/1 returns a notification_preference changeset" do - notification_preference = insert(:notification_preference) + notification_type = insert(:notification_type, name: "change_notification_preference/1") + notification_config = insert(:notification_config, notification_type: notification_type) + + notification_preference = + insert(:notification_preference, notification_config: notification_config) assert %Ecto.Changeset{} = Notifications.change_notification_preference(notification_preference) From ba292010fc9049b456bbd426f44db2963ac9fcb4 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Sun, 26 Feb 2023 20:20:47 +0800 Subject: [PATCH 40/80] feat: Implement job for assessment submission mail --- lib/cadet/accounts/course_registrations.ex | 17 +++++++++ lib/cadet/assessments/assessments.ex | 12 ++++++ lib/cadet/email.ex | 17 +++++++++ lib/cadet/notifications.ex | 4 ++ lib/cadet/workers/NotificationWorker.ex | 38 +++++++++++++++++++ .../email/assessment_submission.html.eex | 5 +++ ...ssessment_submission_notification_type.exs | 13 +++++++ 7 files changed, 106 insertions(+) create mode 100644 lib/cadet_web/templates/email/assessment_submission.html.eex create mode 100644 priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs diff --git a/lib/cadet/accounts/course_registrations.ex b/lib/cadet/accounts/course_registrations.ex index bdb6d9260..787cb4c97 100644 --- a/lib/cadet/accounts/course_registrations.ex +++ b/lib/cadet/accounts/course_registrations.ex @@ -207,4 +207,21 @@ defmodule Cadet.Accounts.CourseRegistrations do {:error, changeset} -> {:error, {:bad_request, full_error_messages(changeset)}} end end + + def get_avenger_of(student_id) when is_ecto_id(student_id) do + CourseRegistration + |> Repo.get_by(id: student_id) + |> Repo.preload(:group) + |> Map.get(:group) + |> case do + nil -> nil + group -> + avenger_id = Map.get(group, :leader_id) + + CourseRegistration + |> where([cr], cr.id == ^avenger_id) + |> preload(:user) + |> Repo.one() + end + end end diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index 0dfe7ebd1..3210a8f27 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -721,11 +721,23 @@ defmodule Cadet.Assessments do |> Repo.one() end + def get_submission_by_id(submission_id) when is_ecto_id(submission_id) do + Submission + |> where(id: ^submission_id) + |> join(:inner, [s], a in assoc(s, :assessment)) + |> preload([_, a], assessment: a) + |> Repo.one() + end + def finalise_submission(submission = %Submission{}) do with {:status, :attempted} <- {:status, submission.status}, {:ok, updated_submission} <- update_submission_status_and_xp_bonus(submission) do # Couple with update_submission_status_and_xp_bonus to ensure notification is sent Notifications.write_notification_when_student_submits(submission) + # Send email notification to avenger + %{notification_type: "assessment_submission", submission_id: updated_submission.id} + |> Cadet.Workers.NotificationWorker.new() + |> Oban.insert() # Begin autograding job GradingJob.force_grade_individual_submission(updated_submission) diff --git a/lib/cadet/email.ex b/lib/cadet/email.ex index 3d471d37a..961928b55 100644 --- a/lib/cadet/email.ex +++ b/lib/cadet/email.ex @@ -20,6 +20,23 @@ defmodule Cadet.Email do end end + def assessment_submission_email(template_file_name, avenger, student, submission) do + cond do + is_nil(avenger.email) -> + nil + + true -> + base_email() + |> to(avenger.email) + |> assign(:avenger_name, avenger.name) + |> assign(:student_name, student.name) + |> assign(:assessment_title, submission.assessment.title) + |> subject("New submission for #{submission.assessment.title}") + |> render("#{template_file_name}.html") + |> Mailer.deliver_now() + end + end + defp base_email do new_email() |> from("noreply@sourceacademy.org") diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 7c84600c0..1a8e0b13d 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -30,6 +30,10 @@ defmodule Cadet.Notifications do """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) + def get_notification_type_by_name!(name) do + Repo.one!(from(nt in NotificationType, where: nt.name == ^name)) + end + @doc """ Gets a single notification_type by name.any() diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index 22bf51c79..d1456e8e2 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -90,4 +90,42 @@ defmodule Cadet.Workers.NotificationWorker do :ok end + + @impl Oban.Worker + def perform(%Oban.Job{ + args: + %{"notification_type" => notification_type, "submission_id" => submission_id} = _args + }) + when notification_type == "assessment_submission" do + notification_type = + Cadet.Notifications.get_notification_type_by_name!("ASSESSMENT SUBMISSION") + + submission = Cadet.Assessments.get_submission_by_id(submission_id) + course_id = submission.assessment.course_id + student_id = submission.student_id + assement_config_id = submission.assessment.config_id + student = Cadet.Accounts.get_user(student_id) + avenger = Cadet.Accounts.CourseRegistrations.get_avenger_of(student_id).user + + cond do + !is_system_enabled(notification_type.id) -> + IO.puts("[ASSESSMENT_SUBMISSION] system-level disabled!") + + !is_course_enabled(notification_type.id, course_id, assement_config_id) -> + IO.puts("[ASSESSMENT_SUBMISSION] course-level disabled") + + !is_user_enabled(notification_type.id, submission.student_id) -> + IO.puts("[ASSESSMENT_SUBMISSION] user-level disabled") + + true -> + IO.puts("[ASSESSMENT_SUBMISSION] SENDING_OUT") + + Email.assessment_submission_email( + notification_type.template_file_name, + avenger, + student, + submission + ) + end + end end diff --git a/lib/cadet_web/templates/email/assessment_submission.html.eex b/lib/cadet_web/templates/email/assessment_submission.html.eex new file mode 100644 index 000000000..240c72dcd --- /dev/null +++ b/lib/cadet_web/templates/email/assessment_submission.html.eex @@ -0,0 +1,5 @@ +

Dear <%= @avenger_name %>,

+ +

There is a new submission by <%= @student_name %> for <%= @assessment_title %>. Please Review and grade the submission

+ +Unsubscribe from this email topic. diff --git a/priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs b/priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs new file mode 100644 index 000000000..1b5b3fd04 --- /dev/null +++ b/priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs @@ -0,0 +1,13 @@ +defmodule Cadet.Repo.Migrations.AddAssessmentSubmissionNotificationType do + use Ecto.Migration + + def up do + execute( + "INSERT INTO notification_types (name, template_file_name, is_autopopulated, inserted_at, updated_at) VALUES ('ASSESSMENT SUBMISSION', 'assessment_submission', TRUE, current_timestamp, current_timestamp)" + ) + end + + def down do + execute("DELETE FROM notification_types WHERE name = 'ASSESSMENT SUBMISSION'") + end +end From 4f120ee91f9731ac96441e6b5d29f083b5e45821 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 11 Mar 2023 01:05:25 +0800 Subject: [PATCH 41/80] fix: Fix assessment submission worker --- lib/cadet/workers/NotificationWorker.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index d1456e8e2..c9830e827 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -4,6 +4,7 @@ defmodule Cadet.Workers.NotificationWorker do """ use Oban.Worker, queue: :notifications, max_attempts: 1 alias Cadet.{Email, Notifications} + alias Cadet.Repo defp is_system_enabled(notification_type_id) do Notifications.get_notification_type!(notification_type_id).is_enabled @@ -104,7 +105,8 @@ defmodule Cadet.Workers.NotificationWorker do course_id = submission.assessment.course_id student_id = submission.student_id assement_config_id = submission.assessment.config_id - student = Cadet.Accounts.get_user(student_id) + course_reg = Repo.get(Cadet.Accounts.CourseRegistration, submission.student_id) + student = Cadet.Accounts.get_user(course_reg.user_id) avenger = Cadet.Accounts.CourseRegistrations.get_avenger_of(student_id).user cond do From 0b245a9ddbeb197e77050639ddd08e16e4ed6b30 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 11 Mar 2023 01:06:08 +0800 Subject: [PATCH 42/80] chore: Add test for assessment submission --- .../notification_worker_test.exs | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/test/cadet/jobs/notification_worker/notification_worker_test.exs b/test/cadet/jobs/notification_worker/notification_worker_test.exs index ba3e8fe83..d15acb9b0 100644 --- a/test/cadet/jobs/notification_worker/notification_worker_test.exs +++ b/test/cadet/jobs/notification_worker/notification_worker_test.exs @@ -12,6 +12,21 @@ defmodule Cadet.NotificationWorker.NotificationWorkerTest do assessments = Cadet.Test.Seeds.assessments() avenger_cr = assessments.course_regs.avenger1_cr + # setup for assessment submission + asssub_ntype = Cadet.Notifications.get_notification_type_by_name!("ASSESSMENT SUBMISSION") + {name, data} = Enum.at(assessments.assessments, 0) + submission = List.first(List.first(data.mcq_answers)).submission + + assessment_config = submission.assessment.config + course = assessment_config.course + insert(:notification_config, + notification_type: asssub_ntype, + course: course, + assessment_config: assessment_config, + is_enabled: true + ) + + # setup for avenger backlog ungraded_submissions = Jason.decode!( elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), 1) @@ -20,25 +35,33 @@ defmodule Cadet.NotificationWorker.NotificationWorkerTest do Repo.update_all(NotificationType, set: [is_enabled: true]) Repo.update_all(NotificationConfig, set: [is_enabled: true]) - {:ok, %{avenger_user: avenger_cr.user, ungraded_submissions: ungraded_submissions}} + {:ok, + %{ + avenger_user: avenger_cr.user, + ungraded_submissions: ungraded_submissions, + submission_id: submission.id + }} end test "avenger backlog test", %{ avenger_user: avenger_user } do perform_job(NotificationWorker, %{"notification_type" => "avenger_backlog"}) - # ntype = Cadet.Notifications.get_notification_type!(1) - # email = - # Cadet.Email.avenger_backlog_email( - # ntype.template_file_name, - # avenger_user, - # ungraded_submissions - # ) - - # assert_delivered_email(email) avenger_email = avenger_user.email + assert_delivered_email_matches(%{to: [{_, ^avenger_email}]}) + end + + test "assessment submission test", %{ + avenger_user: avenger_user, + submission_id: submission_id + } do + perform_job(NotificationWorker, %{ + "notification_type" => "assessment_submission", + submission_id: submission_id + }) + avenger_email = avenger_user.email assert_delivered_email_matches(%{to: [{_, ^avenger_email}]}) end end From 9600d405a525a99254f5716c04ca9fa0625b4ca8 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 11 Mar 2023 18:59:26 +0800 Subject: [PATCH 43/80] chore: Add migration to populate existing nus users' emails Current nus users will have their email attribute populated based on their username. nus users are identified from the provider attribute. Future nus users will have their email attribute populated on creation ideally. --- .../20230311105547_populate_nus_student_emails.exs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 priv/repo/migrations/20230311105547_populate_nus_student_emails.exs diff --git a/priv/repo/migrations/20230311105547_populate_nus_student_emails.exs b/priv/repo/migrations/20230311105547_populate_nus_student_emails.exs new file mode 100644 index 000000000..ffb77b519 --- /dev/null +++ b/priv/repo/migrations/20230311105547_populate_nus_student_emails.exs @@ -0,0 +1,11 @@ +defmodule Cadet.Repo.Migrations.PopulateNusStudentEmails do + use Ecto.Migration + + def change do + execute(" + update users + set email = username || '@u.nus.edu' + where username ~ '^[eE][0-9]{7}$' and email IS NULL and provider = 'luminus'; + ") + end +end From 22c1391edfd1c12be9ef98aaa67bbcd715f16d83 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Mon, 13 Mar 2023 00:08:13 +0800 Subject: [PATCH 44/80] feat: implement sent_notifications - move mailing logic to notification worker - insert into sent_notifications when email is sent out successfully --- lib/cadet/email.ex | 25 ++++++------- lib/cadet/notifications.ex | 48 ++++++++++++++++--------- lib/cadet/workers/NotificationWorker.ex | 48 ++++++++++++++++++------- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/lib/cadet/email.ex b/lib/cadet/email.ex index 961928b55..b7ff08101 100644 --- a/lib/cadet/email.ex +++ b/lib/cadet/email.ex @@ -4,7 +4,6 @@ defmodule Cadet.Email do """ use Bamboo.Phoenix, view: CadetWeb.EmailView import Bamboo.Email - alias Cadet.Mailer def avenger_backlog_email(template_file_name, avenger, ungraded_submissions) do if is_nil(avenger.email) do @@ -16,24 +15,20 @@ defmodule Cadet.Email do |> assign(:submissions, ungraded_submissions) |> subject("Backlog for #{avenger.name}") |> render("#{template_file_name}.html") - |> Mailer.deliver_now() end end def assessment_submission_email(template_file_name, avenger, student, submission) do - cond do - is_nil(avenger.email) -> - nil - - true -> - base_email() - |> to(avenger.email) - |> assign(:avenger_name, avenger.name) - |> assign(:student_name, student.name) - |> assign(:assessment_title, submission.assessment.title) - |> subject("New submission for #{submission.assessment.title}") - |> render("#{template_file_name}.html") - |> Mailer.deliver_now() + if is_nil(avenger.email) do + nil + else + base_email() + |> to(avenger.email) + |> assign(:avenger_name, avenger.name) + |> assign(:student_name, student.name) + |> assign(:assessment_title, submission.assessment.title) + |> subject("New submission for #{submission.assessment.title}") + |> render("#{template_file_name}.html") end end diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 1a8e0b13d..022e59f18 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -9,7 +9,7 @@ defmodule Cadet.Notifications do alias Cadet.Notifications.{ NotificationType, NotificationConfig, - # SentNotification, + SentNotification, TimeOption, NotificationPreference } @@ -30,10 +30,6 @@ defmodule Cadet.Notifications do """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) - def get_notification_type_by_name!(name) do - Repo.one!(from(nt in NotificationType, where: nt.name == ^name)) - end - @doc """ Gets a single notification_type by name.any() @@ -51,7 +47,7 @@ defmodule Cadet.Notifications do Repo.one!(from(nt in NotificationType, where: nt.name == ^name)) end - def get_notification_config!(notification_type_id, course_id, assconfig_id) do + def get_notification_config(notification_type_id, course_id, assconfig_id) do query = from(n in Cadet.Notifications.NotificationConfig, join: ntype in Cadet.Notifications.NotificationType, @@ -66,7 +62,7 @@ defmodule Cadet.Notifications do where(query, [c], c.assessment_config_id == ^assconfig_id) end - Repo.one!(query) + Repo.one(query) end @doc """ @@ -262,6 +258,24 @@ defmodule Cadet.Notifications do NotificationPreference.changeset(notification_preference, attrs) end + @doc """ + Creates a sent_notification. + + ## Examples + + iex> create_sent_notification(%{field: value}) + {:ok, %SentNotification{}} + + iex> create_sent_notification(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_sent_notification(course_reg_id, content) do + %SentNotification{} + |> SentNotification.changeset(%{course_reg_id: course_reg_id, content: content}) + |> Repo.insert() + end + @doc """ Returns the list of sent_notifications. @@ -276,19 +290,19 @@ defmodule Cadet.Notifications do # Repo.all(SentNotification) # end - @doc """ - Gets a single sent_notification. + # @doc """ + # Gets a single sent_notification. - Raises `Ecto.NoResultsError` if the Sent notification does not exist. + # Raises `Ecto.NoResultsError` if the Sent notification does not exist. - ## Examples + # ## Examples - iex> get_sent_notification!(123) - %SentNotification{} + # iex> get_sent_notification!(123) + # %SentNotification{} - iex> get_sent_notification!(456) - ** (Ecto.NoResultsError) + # iex> get_sent_notification!(456) + # ** (Ecto.NoResultsError) - """ - # def get_sent_notification!(id), do: Repo.get!(SentNotification, id) + # """ + # # def get_sent_notification!(id), do: Repo.get!(SentNotification, id) end diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index c9830e827..14e50df59 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -3,7 +3,7 @@ defmodule Cadet.Workers.NotificationWorker do Contain oban workers for sending notifications """ use Oban.Worker, queue: :notifications, max_attempts: 1 - alias Cadet.{Email, Notifications} + alias Cadet.{Email, Notifications, Mailer} alias Cadet.Repo defp is_system_enabled(notification_type_id) do @@ -11,11 +11,18 @@ defmodule Cadet.Workers.NotificationWorker do end defp is_course_enabled(notification_type_id, course_id, assessment_config_id) do - Notifications.get_notification_config!( - notification_type_id, - course_id, - assessment_config_id - ).is_enabled + notification_config = + Notifications.get_notification_config( + notification_type_id, + course_id, + assessment_config_id + ) + + if is_nil(notification_config) do + false + else + notification_config.is_enabled + end end defp is_user_enabled(notification_type_id, course_reg_id) do @@ -84,7 +91,15 @@ defmodule Cadet.Workers.NotificationWorker do true -> IO.puts("[AVENGER_BACKLOG] SENDING_OUT") - Email.avenger_backlog_email(ntype.template_file_name, avenger, ungraded_submissions) + + email = + Email.avenger_backlog_email(ntype.template_file_name, avenger, ungraded_submissions) + + {status, email} = Mailer.deliver_now(email) + + if status == :ok do + Notifications.create_sent_notification(avenger_cr.id, email.html_body) + end end end end @@ -122,12 +137,19 @@ defmodule Cadet.Workers.NotificationWorker do true -> IO.puts("[ASSESSMENT_SUBMISSION] SENDING_OUT") - Email.assessment_submission_email( - notification_type.template_file_name, - avenger, - student, - submission - ) + email = + Email.assessment_submission_email( + notification_type.template_file_name, + avenger, + student, + submission + ) + + {status, email} = Mailer.deliver_now(email) + + if status == :ok do + Notifications.create_sent_notification(course_reg.id, email.html_body) + end end end end From 8ef9a5949545805ff6b68ad5a95c6a55efc0920b Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Mon, 13 Mar 2023 01:11:44 +0800 Subject: [PATCH 45/80] fix: fix tests --- lib/cadet/notifications.ex | 2 +- lib/cadet/workers/NotificationWorker.ex | 2 +- test/cadet/email_test.exs | 47 +++++++++++++++++-- .../notification_worker_test.exs | 3 +- .../notifications/notifications_test.exs | 19 ++++++-- 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 022e59f18..cc65d529a 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -47,7 +47,7 @@ defmodule Cadet.Notifications do Repo.one!(from(nt in NotificationType, where: nt.name == ^name)) end - def get_notification_config(notification_type_id, course_id, assconfig_id) do + def get_notification_config!(notification_type_id, course_id, assconfig_id) do query = from(n in Cadet.Notifications.NotificationConfig, join: ntype in Cadet.Notifications.NotificationType, diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index 14e50df59..8ad54ad0e 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -12,7 +12,7 @@ defmodule Cadet.Workers.NotificationWorker do defp is_course_enabled(notification_type_id, course_id, assessment_config_id) do notification_config = - Notifications.get_notification_config( + Notifications.get_notification_config!( notification_type_id, course_id, assessment_config_id diff --git a/test/cadet/email_test.exs b/test/cadet/email_test.exs index 7e925a383..462daad65 100644 --- a/test/cadet/email_test.exs +++ b/test/cadet/email_test.exs @@ -1,10 +1,25 @@ defmodule Cadet.EmailTest do use ExUnit.Case use Bamboo.Test - alias Cadet.Email + alias Cadet.{Email, Repo, Accounts} + alias Cadet.Assessments.Submission use Cadet.ChangesetCase, entity: Email + setup do + Cadet.Test.Seeds.assessments() + + submission = + Cadet.Assessments.Submission + |> Repo.all() + |> Repo.preload([:assessment]) + + {:ok, + %{ + submission: submission |> List.first() + }} + end + test "avenger backlog email" do avenger_user = insert(:user, %{email: "test@gmail.com"}) avenger = insert(:course_registration, %{user: avenger_user, role: :staff}) @@ -14,9 +29,35 @@ defmodule Cadet.EmailTest do elem(Cadet.Assessments.all_submissions_by_grader_for_index(avenger, true, true), 1) ) - Cadet.Email.avenger_backlog_email("avenger_backlog", avenger_user, ungraded_submissions) + email = Email.avenger_backlog_email("avenger_backlog", avenger_user, ungraded_submissions) avenger_email = avenger_user.email - assert_delivered_email_matches(%{to: [{_, ^avenger_email}]}) + assert email.to == avenger_email + assert email.subject == "Backlog for #{avenger_user.name}" + end + + test "assessment submission email", %{ + submission: submission + } do + submission + |> Submission.changeset(%{status: :submitted}) + |> Repo.update() + + student_id = submission.student_id + course_reg = Repo.get(Accounts.CourseRegistration, submission.student_id) + student = Accounts.get_user(course_reg.user_id) + avenger = Accounts.CourseRegistrations.get_avenger_of(student_id).user + + email = + Email.assessment_submission_email( + "assessment_submission", + avenger, + student, + submission + ) + + avenger_email = avenger.email + assert email.to == avenger_email + assert email.subject == "New submission for #{submission.assessment.title}" end end diff --git a/test/cadet/jobs/notification_worker/notification_worker_test.exs b/test/cadet/jobs/notification_worker/notification_worker_test.exs index d15acb9b0..7fa70a70b 100644 --- a/test/cadet/jobs/notification_worker/notification_worker_test.exs +++ b/test/cadet/jobs/notification_worker/notification_worker_test.exs @@ -14,11 +14,12 @@ defmodule Cadet.NotificationWorker.NotificationWorkerTest do # setup for assessment submission asssub_ntype = Cadet.Notifications.get_notification_type_by_name!("ASSESSMENT SUBMISSION") - {name, data} = Enum.at(assessments.assessments, 0) + {_name, data} = Enum.at(assessments.assessments, 0) submission = List.first(List.first(data.mcq_answers)).submission assessment_config = submission.assessment.config course = assessment_config.course + insert(:notification_config, notification_type: asssub_ntype, course: course, diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index ef32afd34..5640eeebd 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -193,11 +193,24 @@ defmodule Cadet.NotificationsTest do end describe "sent_notifications" do - # alias Cadet.Notifications.SentNotification + alias Cadet.Notifications.SentNotification - # import Cadet.NotificationsFixtures + setup do + course = insert(:course) + course_reg = insert(:course_registration, course: course) + {:ok, course_reg: course_reg} + end + + test "create_sent_notification/1 with valid data creates a sent_notification", + %{course_reg: course_reg} do + assert {:ok, %SentNotification{}} = + Notifications.create_sent_notification(course_reg.id, "test content") + end - # @invalid_attrs %{content: nil} + test "create_sent_notification/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = + Notifications.create_sent_notification(nil, "test content") + end # test "list_sent_notifications/0 returns all sent_notifications" do # sent_notification = sent_notification_fixture() From a516463b36c9a6ee3fe7e9f3749b439ede5573d5 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Mon, 13 Mar 2023 01:12:13 +0800 Subject: [PATCH 46/80] style: fix formatting --- lib/cadet/accounts/course_registrations.ex | 4 +++- lib/cadet/assessments/assessments.ex | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/cadet/accounts/course_registrations.ex b/lib/cadet/accounts/course_registrations.ex index 787cb4c97..c45549199 100644 --- a/lib/cadet/accounts/course_registrations.ex +++ b/lib/cadet/accounts/course_registrations.ex @@ -214,7 +214,9 @@ defmodule Cadet.Accounts.CourseRegistrations do |> Repo.preload(:group) |> Map.get(:group) |> case do - nil -> nil + nil -> + nil + group -> avenger_id = Map.get(group, :leader_id) diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index 3210a8f27..90f350421 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -736,8 +736,9 @@ defmodule Cadet.Assessments do Notifications.write_notification_when_student_submits(submission) # Send email notification to avenger %{notification_type: "assessment_submission", submission_id: updated_submission.id} - |> Cadet.Workers.NotificationWorker.new() - |> Oban.insert() + |> Cadet.Workers.NotificationWorker.new() + |> Oban.insert() + # Begin autograding job GradingJob.force_grade_individual_submission(updated_submission) From 899494775e6db1a1462b20f8df7a1ef1694da7f2 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Mon, 13 Mar 2023 12:51:58 +0800 Subject: [PATCH 47/80] fix: fix guard clauses - move guard clauses to prevent unnecessary querying --- lib/cadet/workers/NotificationWorker.ex | 138 ++++++++++++------------ 1 file changed, 72 insertions(+), 66 deletions(-) diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index 8ad54ad0e..d96a3df22 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -65,43 +65,47 @@ defmodule Cadet.Workers.NotificationWorker do ntype = Cadet.Notifications.get_notification_type_by_name!("AVENGER BACKLOG") notification_type_id = ntype.id - for course_id <- Cadet.Courses.get_all_course_ids() do - avengers_crs = Cadet.Accounts.CourseRegistrations.get_staffs(course_id) - - for avenger_cr <- avengers_crs do - avenger = Cadet.Accounts.get_user(avenger_cr.user_id) - - ungraded_submissions = - Jason.decode!( - elem( - Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), - 1 - ) - ) - - cond do - length(ungraded_submissions) < ungraded_threshold -> - IO.puts("[AVENGER_BACKLOG] below threshold!") - - !is_system_enabled(notification_type_id) -> - IO.puts("[AVENGER_BACKLOG] system-level disabled!") - - !is_course_enabled(notification_type_id, course_id, nil) -> - IO.puts("[AVENGER_BACKLOG] course-level disabled") - - true -> - IO.puts("[AVENGER_BACKLOG] SENDING_OUT") - - email = - Email.avenger_backlog_email(ntype.template_file_name, avenger, ungraded_submissions) - - {status, email} = Mailer.deliver_now(email) - - if status == :ok do - Notifications.create_sent_notification(avenger_cr.id, email.html_body) + if is_system_enabled(notification_type_id) do + for course_id <- Cadet.Courses.get_all_course_ids() do + if is_course_enabled(notification_type_id, course_id, nil) do + avengers_crs = Cadet.Accounts.CourseRegistrations.get_staffs(course_id) + + for avenger_cr <- avengers_crs do + avenger = Cadet.Accounts.get_user(avenger_cr.user_id) + + ungraded_submissions = + Jason.decode!( + elem( + Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), + 1 + ) + ) + + if length(ungraded_submissions) < ungraded_threshold do + IO.puts("[AVENGER_BACKLOG] below threshold!") + else + IO.puts("[AVENGER_BACKLOG] SENDING_OUT") + + email = + Email.avenger_backlog_email( + ntype.template_file_name, + avenger, + ungraded_submissions + ) + + {status, email} = Mailer.deliver_now(email) + + if status == :ok do + Notifications.create_sent_notification(avenger_cr.id, email.html_body) + end end + end + else + IO.puts("[AVENGER_BACKLOG] course-level disabled") end end + else + IO.puts("[AVENGER_BACKLOG] system-level disabled!") end :ok @@ -116,40 +120,42 @@ defmodule Cadet.Workers.NotificationWorker do notification_type = Cadet.Notifications.get_notification_type_by_name!("ASSESSMENT SUBMISSION") - submission = Cadet.Assessments.get_submission_by_id(submission_id) - course_id = submission.assessment.course_id - student_id = submission.student_id - assement_config_id = submission.assessment.config_id - course_reg = Repo.get(Cadet.Accounts.CourseRegistration, submission.student_id) - student = Cadet.Accounts.get_user(course_reg.user_id) - avenger = Cadet.Accounts.CourseRegistrations.get_avenger_of(student_id).user - - cond do - !is_system_enabled(notification_type.id) -> - IO.puts("[ASSESSMENT_SUBMISSION] system-level disabled!") - - !is_course_enabled(notification_type.id, course_id, assement_config_id) -> - IO.puts("[ASSESSMENT_SUBMISSION] course-level disabled") - - !is_user_enabled(notification_type.id, submission.student_id) -> - IO.puts("[ASSESSMENT_SUBMISSION] user-level disabled") - - true -> - IO.puts("[ASSESSMENT_SUBMISSION] SENDING_OUT") - - email = - Email.assessment_submission_email( - notification_type.template_file_name, - avenger, - student, - submission - ) + if is_system_enabled(notification_type.id) do + submission = Cadet.Assessments.get_submission_by_id(submission_id) + course_id = submission.assessment.course_id + student_id = submission.student_id + assessment_config_id = submission.assessment.config_id + course_reg = Repo.get(Cadet.Accounts.CourseRegistration, submission.student_id) + student = Cadet.Accounts.get_user(course_reg.user_id) + avenger_cr = Cadet.Accounts.CourseRegistrations.get_avenger_of(student_id) + avenger = avenger_cr.user + + cond do + !is_course_enabled(notification_type.id, course_id, assessment_config_id) -> + IO.puts("[ASSESSMENT_SUBMISSION] course-level disabled") + + !is_user_enabled(notification_type.id, avenger_cr.id) -> + IO.puts("[ASSESSMENT_SUBMISSION] user-level disabled") + + true -> + IO.puts("[ASSESSMENT_SUBMISSION] SENDING_OUT") + + email = + Email.assessment_submission_email( + notification_type.template_file_name, + avenger, + student, + submission + ) - {status, email} = Mailer.deliver_now(email) + {status, email} = Mailer.deliver_now(email) - if status == :ok do - Notifications.create_sent_notification(course_reg.id, email.html_body) - end + if status == :ok do + Notifications.create_sent_notification(course_reg.id, email.html_body) + end + end + else + IO.puts("[ASSESSMENT_SUBMISSION] system-level disabled!") end end end From b93cdbdf7a0a86a10306b341597d5721ae26f239 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 13 Mar 2023 16:32:21 +0800 Subject: [PATCH 48/80] fix: Fix db triggers not running for assessment submission notifications --- ...400_add_assessment_submission_notification_type.exs | 2 +- .../notification_worker/notification_worker_test.exs | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs b/priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs index 1b5b3fd04..b7d02efa4 100644 --- a/priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs +++ b/priv/repo/migrations/20230215072400_add_assessment_submission_notification_type.exs @@ -3,7 +3,7 @@ defmodule Cadet.Repo.Migrations.AddAssessmentSubmissionNotificationType do def up do execute( - "INSERT INTO notification_types (name, template_file_name, is_autopopulated, inserted_at, updated_at) VALUES ('ASSESSMENT SUBMISSION', 'assessment_submission', TRUE, current_timestamp, current_timestamp)" + "INSERT INTO notification_types (name, template_file_name, is_autopopulated, inserted_at, updated_at) VALUES ('ASSESSMENT SUBMISSION', 'assessment_submission', FALSE, current_timestamp, current_timestamp)" ) end diff --git a/test/cadet/jobs/notification_worker/notification_worker_test.exs b/test/cadet/jobs/notification_worker/notification_worker_test.exs index 7fa70a70b..41606d4ce 100644 --- a/test/cadet/jobs/notification_worker/notification_worker_test.exs +++ b/test/cadet/jobs/notification_worker/notification_worker_test.exs @@ -17,16 +17,6 @@ defmodule Cadet.NotificationWorker.NotificationWorkerTest do {_name, data} = Enum.at(assessments.assessments, 0) submission = List.first(List.first(data.mcq_answers)).submission - assessment_config = submission.assessment.config - course = assessment_config.course - - insert(:notification_config, - notification_type: asssub_ntype, - course: course, - assessment_config: assessment_config, - is_enabled: true - ) - # setup for avenger backlog ungraded_submissions = Jason.decode!( From deca215ae0997b0e312229924d126d44caebd5d6 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sun, 19 Mar 2023 02:12:30 +0800 Subject: [PATCH 49/80] feat: Add endpoints for notifications --- lib/cadet/notifications.ex | 102 ++++++++++++++- .../notifications/notification_config.ex | 5 +- lib/cadet/notifications/notification_type.ex | 5 +- .../new_notifications_controller.ex | 118 ++++++++++++++++++ lib/cadet_web/router.ex | 14 +++ lib/cadet_web/views/new_notifications_view.ex | 95 ++++++++++++++ ...otification_types_add_for_staff_column.exs | 9 ++ 7 files changed, 344 insertions(+), 4 deletions(-) create mode 100644 lib/cadet_web/controllers/new_notifications_controller.ex create mode 100644 lib/cadet_web/views/new_notifications_view.ex create mode 100644 priv/repo/migrations/20230315053558_notification_types_add_for_staff_column.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index cc65d529a..2befa675f 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -14,6 +14,8 @@ defmodule Cadet.Notifications do NotificationPreference } + alias Cadet.Accounts.Role + @doc """ Gets a single notification_type. @@ -62,7 +64,67 @@ defmodule Cadet.Notifications do where(query, [c], c.assessment_config_id == ^assconfig_id) end - Repo.one(query) + Repo.one!(query) + end + + @doc """ + Gets all notification configs that belong to a course + """ + def get_notification_configs(course_id) do + query = + from(n in Cadet.Notifications.NotificationConfig, + join: ntype in Cadet.Notifications.NotificationType, + on: n.notification_type_id == ntype.id, + join: c in Cadet.Courses.Course, + on: n.course_id == c.id, + left_join: ac in Cadet.Courses.AssessmentConfig, + on: n.assessment_config_id == ac.id, + left_join: to in Cadet.Notifications.TimeOption, + on: to.notification_config_id == n.id, + where: n.course_id == ^course_id + ) + + query + |> Repo.all() + |> Repo.preload([:notification_type, :course, :assessment_config, :time_options]) + end + + @doc """ + Gets all notification configs with preferences that + 1. belongs to the course of the course reg, + 2. only notifications that it can configure based on course reg's role + """ + def get_configurable_notification_configs(cr_id) do + cr = Repo.get(Cadet.Accounts.CourseRegistration, cr_id) + is_staff = cr.role == :staff + + query = + from(n in Cadet.Notifications.NotificationConfig, + join: ntype in Cadet.Notifications.NotificationType, + on: n.notification_type_id == ntype.id, + join: c in Cadet.Courses.Course, + on: n.course_id == c.id, + left_join: ac in Cadet.Courses.AssessmentConfig, + on: n.assessment_config_id == ac.id, + left_join: to in Cadet.Notifications.TimeOption, + on: to.notification_config_id == n.id, + left_join: p in Cadet.Notifications.NotificationPreference, + on: p.notification_config_id == n.id, + where: + ntype.for_staff == ^is_staff and + n.course_id == ^cr.course_id and + p.course_reg_id == ^cr.id + ) + + query + |> Repo.all() + |> Repo.preload([ + :notification_type, + :course, + :assessment_config, + :time_options, + :notification_preferences + ]) end @doc """ @@ -112,6 +174,23 @@ defmodule Cadet.Notifications do """ def get_time_option!(id), do: Repo.get!(TimeOption, id) + @doc """ + Gets all time options for a notification config + """ + def get_time_options_for_config(notification_config_id) do + query = + from(to in Cadet.Notifications.TimeOption, + join: nc in Cadet.Notifications.NotificationConfig, + on: to.notification_config_id == nc.id, + where: nc.id == ^notification_config_id + ) + + Repo.all(query) + end + + @doc """ + Gets all time options for an assessment config and notification type + """ def get_time_options_for_assessment(assessment_config_id, notification_type_id) do query = from(ac in Cadet.Courses.AssessmentConfig, @@ -126,6 +205,9 @@ defmodule Cadet.Notifications do Repo.all(query) end + @doc """ + Gets the default time options for an assessment config and notification type + """ def get_default_time_option_for_assessment!(assessment_config_id, notification_type_id) do query = from(ac in Cadet.Courses.AssessmentConfig, @@ -176,6 +258,24 @@ defmodule Cadet.Notifications do Repo.delete(time_option) end + @doc """ + Gets the notification preference based from its id + """ + def get_notification_preference!(notification_preference_id) do + query = + from(np in NotificationPreference, + left_join: to in TimeOption, + on: to.id == np.time_option_id, + where: np.id == ^notification_preference_id, + preload: :time_option + ) + + Repo.one!(query) + end + + @doc """ + Gets the notification preference based from notification type and course reg + """ def get_notification_preference(notification_type_id, course_reg_id) do query = from(np in NotificationPreference, diff --git a/lib/cadet/notifications/notification_config.ex b/lib/cadet/notifications/notification_config.ex index 2072b9f45..355bcf2a3 100644 --- a/lib/cadet/notifications/notification_config.ex +++ b/lib/cadet/notifications/notification_config.ex @@ -5,7 +5,7 @@ defmodule Cadet.Notifications.NotificationConfig do use Ecto.Schema import Ecto.Changeset alias Cadet.Courses.{Course, AssessmentConfig} - alias Cadet.Notifications.NotificationType + alias Cadet.Notifications.{NotificationType, TimeOption, NotificationPreference} schema "notification_configs" do field(:is_enabled, :boolean, default: false) @@ -14,6 +14,9 @@ defmodule Cadet.Notifications.NotificationConfig do belongs_to(:course, Course) belongs_to(:assessment_config, AssessmentConfig) + has_many :time_options, TimeOption + has_many :notification_preferences, NotificationPreference + timestamps() end diff --git a/lib/cadet/notifications/notification_type.ex b/lib/cadet/notifications/notification_type.ex index 7f16df022..772d029c0 100644 --- a/lib/cadet/notifications/notification_type.ex +++ b/lib/cadet/notifications/notification_type.ex @@ -12,6 +12,7 @@ defmodule Cadet.Notifications.NotificationType do field(:is_enabled, :boolean, default: false) field(:name, :string) field(:template_file_name, :string) + field(:for_staff, :boolean) timestamps() end @@ -19,8 +20,8 @@ defmodule Cadet.Notifications.NotificationType do @doc false def changeset(notification_type, attrs) do notification_type - |> cast(attrs, [:name, :template_file_name, :is_enabled, :is_autopopulated]) - |> validate_required([:name, :template_file_name, :is_autopopulated]) + |> cast(attrs, [:name, :template_file_name, :is_enabled, :is_autopopulated, :for_staff]) + |> validate_required([:name, :template_file_name, :is_autopopulated, :for_staff]) |> unique_constraint(:name) |> prevent_nil_is_enabled() end diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex new file mode 100644 index 000000000..2b49c1b88 --- /dev/null +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -0,0 +1,118 @@ +defmodule CadetWeb.NewNotificationsController do + use CadetWeb, :controller + + alias Cadet.Repo + alias Cadet.Notifications + alias Cadet.Notifications.{NotificationPreference, TimeOption} + + # NOTIFICATION CONFIGS + + def all_noti_configs(conn, %{"course_id" => course_id}) do + configs = Notifications.get_notification_configs(course_id) + render(conn, "configs.json", configs: configs) + end + + def get_configurable_noti_configs(conn, %{"course_reg_id" => course_reg_id}) do + configs = Notifications.get_configurable_notification_configs(course_reg_id) + render(conn, "configs.json", configs: configs) + end + + # NOTIFICATION PREFERENCES + + def create_preference(conn, params) do + changeset = NotificationPreference.changeset(%NotificationPreference{}, params) + + case Repo.insert(changeset) do + {:ok, res} -> + pref = Notifications.get_notification_preference!(res.id) + render(conn, "preference.json", noti_pref: pref) + + {:error, {status, message}} -> + conn |> put_status(status) |> text(message) + end + end + + def update_preference(conn, %{ + "noti_pref_id" => id, + "is_enabled" => is_enabled, + "time_option_id" => time_option_id + }) do + pref = Repo.get(NotificationPreference, id) + + if is_nil(pref) do + conn |> put_status(404) |> text("Notification preference of given ID not found") + end + + changeset = + pref + |> NotificationPreference.changeset(%{is_enabled: is_enabled}) + |> NotificationPreference.changeset(%{time_option_id: time_option_id}) + + case Repo.update(changeset) do + {:ok, res} -> + pref = Notifications.get_notification_preference!(res.id) + render(conn, "preference.json", noti_pref: pref) + + {:error, {status, message}} -> + conn |> put_status(status) |> text(message) + end + end + + # TIME OPTIONS + + def get_config_time_options(conn, %{"noti_config_id" => noti_config_id}) do + time_options = Notifications.get_time_options_for_config(noti_config_id) + + render(conn, "time_options.json", %{time_options: time_options}) + end + + def create_time_option(conn, params) do + changeset = TimeOption.changeset(%TimeOption{}, params) + + case Repo.insert(changeset) do + {:ok, res} -> + render(conn, "time_option.json", time_option: res) + + {:error, {status, message}} -> + conn |> put_status(status) |> text(message) + end + end + + # Only allow updating of `is_default` + # should delete and create a new entry for other fields + def update_time_option(conn, %{"time_option_id" => time_option_id, "is_default" => is_default}) do + time_option = Repo.get(TimeOption, time_option_id) + + if is_nil(time_option) do + conn |> put_status(404) |> text("Time option of given ID not found") + end + + changeset = + time_option + |> TimeOption.changeset(%{is_default: is_default}) + + case Repo.update(changeset) do + {:ok, res} -> + render(conn, "time_option.json", time_option: res) + + {:error, {status, message}} -> + conn |> put_status(status) |> text(message) + end + end + + def delete_time_option(conn, %{"time_option_id" => time_option_id}) do + time_option = Repo.get(TimeOption, time_option_id) + + if is_nil(time_option) do + conn |> put_status(404) |> text("Time option of given ID not found") + end + + case Repo.delete(time_option) do + {:ok, res} -> + render(conn, "time_option.json", time_option: res) + + {:error, {status, message}} -> + conn |> put_status(status) |> text(message) + end + end +end diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index cbd3fb755..0f6900eb2 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -169,6 +169,20 @@ defmodule CadetWeb.Router do ) end + # Notifications endpoints + scope "/v2/notifications/", CadetWeb do + get("/config/:course_id", NewNotificationsController, :all_noti_configs) + get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) + + post("/preference", NewNotificationsController, :create_preference) + put("/preference/:noti_pref_id", NewNotificationsController, :update_preference) + + get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) + post("/options", NewNotificationsController, :create_time_option) + put("/options/:time_option_id", NewNotificationsController, :update_time_option) + delete("/options/:time_option_id", NewNotificationsController, :delete_time_option) + end + # Other scopes may use custom stacks. # scope "/api", CadetWeb do # pipe_through :api diff --git a/lib/cadet_web/views/new_notifications_view.ex b/lib/cadet_web/views/new_notifications_view.ex new file mode 100644 index 000000000..818d16a87 --- /dev/null +++ b/lib/cadet_web/views/new_notifications_view.ex @@ -0,0 +1,95 @@ +defmodule CadetWeb.NewNotificationsView do + use CadetWeb, :view + + require IEx + + def render("noti_types.json", %{noti_types: noti_types}) do + render_many(noti_types, CadetWeb.NewNotificationsView, "noti_type.json", as: :noti_type) + end + + def render("noti_type.json", %{noti_type: noti_type}) do + render_notification_type(noti_type) + end + + def render("configs.json", %{configs: configs}) do + render_many(configs, CadetWeb.NewNotificationsView, "config.json", as: :config) + end + + def render("config.json", %{config: config}) do + transform_map_for_view(config, %{ + id: :id, + isEnabled: :is_enabled, + notificationType: &render_notification_type(&1.notification_type), + assessmentConfig: &render_assessment_config(&1.assessment_config), + time_options: + &render_many( + &1.time_options, + CadetWeb.NewNotificationsView, + "time_option.json", + as: :time_option + ) + }) + end + + def render("preference.json", %{noti_pref: noti_pref}) do + transform_map_for_view(noti_pref, %{ + id: :id, + isEnabled: :is_enabled, + timeOption: :time_option + }) + end + + def render("time_options.json", %{time_options: time_options}) do + render_many(time_options, CadetWeb.NewNotificationsView, "time_option.json", as: :time_option) + end + + def render("time_option.json", %{time_option: time_option}) do + transform_map_for_view(time_option, %{ + id: :id, + minutes: :minutes, + isDefault: :is_default + }) + end + + defp render_notification_type(noti_type) do + case noti_type do + nil -> + nil + + _ -> + transform_map_for_view(noti_type, %{ + id: :id, + name: :name, + forStaff: :for_staff, + isEnabled: :is_enabled + }) + end + end + + defp render_assessment_config(ass_config) do + case ass_config do + nil -> + nil + + _ -> + transform_map_for_view(ass_config, %{ + id: :id, + type: :type + }) + end + end + + defp render_course(course) do + case course do + nil -> + nil + + _ -> + transform_map_for_view(course, %{ + id: :id, + courseName: :course_name, + courseShortName: :course_short_name + }) + end + end +end diff --git a/priv/repo/migrations/20230315053558_notification_types_add_for_staff_column.exs b/priv/repo/migrations/20230315053558_notification_types_add_for_staff_column.exs new file mode 100644 index 000000000..61f074f94 --- /dev/null +++ b/priv/repo/migrations/20230315053558_notification_types_add_for_staff_column.exs @@ -0,0 +1,9 @@ +defmodule Cadet.Repo.Migrations.NotificationTypesAddForStaffColumn do + use Ecto.Migration + + def change do + alter table(:notification_types) do + add(:for_staff, :boolean, null: false, default: true) + end + end +end From 5c321ca864e647bdfb37bbe8c3e539851a6865ac Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Fri, 24 Mar 2023 02:56:26 +0800 Subject: [PATCH 50/80] fix: Duplicate records returned with multiple time options --- lib/cadet/notifications.ex | 112 +++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 61 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 2befa675f..aaca0b798 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -18,30 +18,30 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_type. - + Raises `Ecto.NoResultsError` if the Notification type does not exist. - + ## Examples - + iex> get_notification_type!(123) %NotificationType{} - + iex> get_notification_type!(456) ** (Ecto.NoResultsError) - + """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) @doc """ Gets a single notification_type by name.any() - + Raises `Ecto.NoResultsError` if the Notification type does not exist. - + ## Examples - + iex> get_notification_type_by_name!("AVENGER BACKLOG") %NotificationType{} - + iex> get_notification_type_by_name!("AVENGER BACKLOG") ** (Ecto.NoResultsError) """ @@ -73,14 +73,6 @@ defmodule Cadet.Notifications do def get_notification_configs(course_id) do query = from(n in Cadet.Notifications.NotificationConfig, - join: ntype in Cadet.Notifications.NotificationType, - on: n.notification_type_id == ntype.id, - join: c in Cadet.Courses.Course, - on: n.course_id == c.id, - left_join: ac in Cadet.Courses.AssessmentConfig, - on: n.assessment_config_id == ac.id, - left_join: to in Cadet.Notifications.TimeOption, - on: to.notification_config_id == n.id, where: n.course_id == ^course_id ) @@ -106,8 +98,6 @@ defmodule Cadet.Notifications do on: n.course_id == c.id, left_join: ac in Cadet.Courses.AssessmentConfig, on: n.assessment_config_id == ac.id, - left_join: to in Cadet.Notifications.TimeOption, - on: to.notification_config_id == n.id, left_join: p in Cadet.Notifications.NotificationPreference, on: p.notification_config_id == n.id, where: @@ -129,15 +119,15 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_config. - + ## Examples - + iex> update_notification_config(notification_config, %{field: new_value}) {:ok, %NotificationConfig{}} - + iex> update_notification_config(notification_config, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_notification_config(notification_config = %NotificationConfig{}, attrs) do notification_config @@ -147,12 +137,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_config changes. - + ## Examples - + iex> change_notification_config(notification_config) %Ecto.Changeset{data: %NotificationConfig{}} - + """ def change_notification_config(notification_config = %NotificationConfig{}, attrs \\ %{}) do NotificationConfig.changeset(notification_config, attrs) @@ -160,17 +150,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single time_option. - + Raises `Ecto.NoResultsError` if the Time option does not exist. - + ## Examples - + iex> get_time_option!(123) %TimeOption{} - + iex> get_time_option!(456) ** (Ecto.NoResultsError) - + """ def get_time_option!(id), do: Repo.get!(TimeOption, id) @@ -226,15 +216,15 @@ defmodule Cadet.Notifications do @doc """ Creates a time_option. - + ## Examples - + iex> create_time_option(%{field: value}) {:ok, %TimeOption{}} - + iex> create_time_option(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_time_option(attrs \\ %{}) do %TimeOption{} @@ -244,15 +234,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a time_option. - + ## Examples - + iex> delete_time_option(time_option) {:ok, %TimeOption{}} - + iex> delete_time_option(time_option) {:error, %Ecto.Changeset{}} - + """ def delete_time_option(time_option = %TimeOption{}) do Repo.delete(time_option) @@ -292,15 +282,15 @@ defmodule Cadet.Notifications do @doc """ Creates a notification_preference. - + ## Examples - + iex> create_notification_preference(%{field: value}) {:ok, %NotificationPreference{}} - + iex> create_notification_preference(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_preference(attrs \\ %{}) do %NotificationPreference{} @@ -310,15 +300,15 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_preference. - + ## Examples - + iex> update_notification_preference(notification_preference, %{field: new_value}) {:ok, %NotificationPreference{}} - + iex> update_notification_preference(notification_preference, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_notification_preference(notification_preference = %NotificationPreference{}, attrs) do notification_preference @@ -328,15 +318,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_preference. - + ## Examples - + iex> delete_notification_preference(notification_preference) {:ok, %NotificationPreference{}} - + iex> delete_notification_preference(notification_preference) {:error, %Ecto.Changeset{}} - + """ def delete_notification_preference(notification_preference = %NotificationPreference{}) do Repo.delete(notification_preference) @@ -344,12 +334,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_preference changes. - + ## Examples - + iex> change_notification_preference(notification_preference) %Ecto.Changeset{data: %NotificationPreference{}} - + """ def change_notification_preference( notification_preference = %NotificationPreference{}, @@ -360,15 +350,15 @@ defmodule Cadet.Notifications do @doc """ Creates a sent_notification. - + ## Examples - + iex> create_sent_notification(%{field: value}) {:ok, %SentNotification{}} - + iex> create_sent_notification(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_sent_notification(course_reg_id, content) do %SentNotification{} @@ -378,12 +368,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of sent_notifications. - + ## Examples - + iex> list_sent_notifications() [%SentNotification{}, ...] - + """ # def list_sent_notifications do From 14221534f504f13317ddda4fbfc2a3cb2f99969b Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Fri, 24 Mar 2023 03:18:11 +0800 Subject: [PATCH 51/80] chore: Update notifications endpoints * Notification configs now return course and notification preferences --- .../new_notifications_controller.ex | 4 ++-- lib/cadet_web/views/new_notifications_view.ex | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index 2b49c1b88..1a7338614 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -25,7 +25,7 @@ defmodule CadetWeb.NewNotificationsController do case Repo.insert(changeset) do {:ok, res} -> pref = Notifications.get_notification_preference!(res.id) - render(conn, "preference.json", noti_pref: pref) + render(conn, "noti_pref.json", noti_pref: pref) {:error, {status, message}} -> conn |> put_status(status) |> text(message) @@ -51,7 +51,7 @@ defmodule CadetWeb.NewNotificationsController do case Repo.update(changeset) do {:ok, res} -> pref = Notifications.get_notification_preference!(res.id) - render(conn, "preference.json", noti_pref: pref) + render(conn, "noti_pref.json", noti_pref: pref) {:error, {status, message}} -> conn |> put_status(status) |> text(message) diff --git a/lib/cadet_web/views/new_notifications_view.ex b/lib/cadet_web/views/new_notifications_view.ex index 818d16a87..904f169fc 100644 --- a/lib/cadet_web/views/new_notifications_view.ex +++ b/lib/cadet_web/views/new_notifications_view.ex @@ -19,9 +19,11 @@ defmodule CadetWeb.NewNotificationsView do transform_map_for_view(config, %{ id: :id, isEnabled: :is_enabled, + course: &render_course(&1.course), notificationType: &render_notification_type(&1.notification_type), assessmentConfig: &render_assessment_config(&1.assessment_config), - time_options: + notificationPreference: &render_many_notification_preferences(&1.notification_preferences), + timeOptions: &render_many( &1.time_options, CadetWeb.NewNotificationsView, @@ -31,11 +33,11 @@ defmodule CadetWeb.NewNotificationsView do }) end - def render("preference.json", %{noti_pref: noti_pref}) do + def render("noti_pref.json", %{noti_pref: noti_pref}) do transform_map_for_view(noti_pref, %{ id: :id, isEnabled: :is_enabled, - timeOption: :time_option + timeOptionId: :time_option_id }) end @@ -66,6 +68,19 @@ defmodule CadetWeb.NewNotificationsView do end end + defp render_many_notification_preferences(noti_prefs) do + case noti_prefs do + nil -> + nil + + %Ecto.Association.NotLoaded{} -> + nil + + _ -> + render_many(noti_prefs, CadetWeb.NewNotificationsView, "noti_pref.json", as: :noti_pref) + end + end + defp render_assessment_config(ass_config) do case ass_config do nil -> From de920be1b947dcad5a4236dee302c38190bae14e Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Mon, 3 Apr 2023 02:59:20 +0800 Subject: [PATCH 52/80] Add more endpoints for notifications UI --- lib/cadet/notifications.ex | 134 +++++++++++------- .../new_notifications_controller.ex | 61 ++++++-- lib/cadet_web/helpers/controller_helper.ex | 12 ++ lib/cadet_web/router.ex | 5 +- lib/cadet_web/views/new_notifications_view.ex | 18 ++- 5 files changed, 165 insertions(+), 65 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index aaca0b798..15f640b87 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -18,30 +18,30 @@ defmodule Cadet.Notifications do @doc """ Gets a single notification_type. - + Raises `Ecto.NoResultsError` if the Notification type does not exist. - + ## Examples - + iex> get_notification_type!(123) %NotificationType{} - + iex> get_notification_type!(456) ** (Ecto.NoResultsError) - + """ def get_notification_type!(id), do: Repo.get!(NotificationType, id) @doc """ Gets a single notification_type by name.any() - + Raises `Ecto.NoResultsError` if the Notification type does not exist. - + ## Examples - + iex> get_notification_type_by_name!("AVENGER BACKLOG") %NotificationType{} - + iex> get_notification_type_by_name!("AVENGER BACKLOG") ** (Ecto.NoResultsError) """ @@ -67,6 +67,8 @@ defmodule Cadet.Notifications do Repo.one!(query) end + def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) + @doc """ Gets all notification configs that belong to a course """ @@ -119,15 +121,15 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_config. - + ## Examples - + iex> update_notification_config(notification_config, %{field: new_value}) {:ok, %NotificationConfig{}} - + iex> update_notification_config(notification_config, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_notification_config(notification_config = %NotificationConfig{}, attrs) do notification_config @@ -137,12 +139,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_config changes. - + ## Examples - + iex> change_notification_config(notification_config) %Ecto.Changeset{data: %NotificationConfig{}} - + """ def change_notification_config(notification_config = %NotificationConfig{}, attrs \\ %{}) do NotificationConfig.changeset(notification_config, attrs) @@ -150,17 +152,17 @@ defmodule Cadet.Notifications do @doc """ Gets a single time_option. - + Raises `Ecto.NoResultsError` if the Time option does not exist. - + ## Examples - + iex> get_time_option!(123) %TimeOption{} - + iex> get_time_option!(456) ** (Ecto.NoResultsError) - + """ def get_time_option!(id), do: Repo.get!(TimeOption, id) @@ -216,15 +218,15 @@ defmodule Cadet.Notifications do @doc """ Creates a time_option. - + ## Examples - + iex> create_time_option(%{field: value}) {:ok, %TimeOption{}} - + iex> create_time_option(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_time_option(attrs \\ %{}) do %TimeOption{} @@ -232,22 +234,52 @@ defmodule Cadet.Notifications do |> Repo.insert() end + @spec upsert_many_time_options([map()]) :: {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} + def upsert_many_time_options(time_options) when is_list(time_options) do + Repo.transaction(fn -> + for to <- time_options do + case Repo.insert(to) do + {:ok, time_option} -> time_option + {:error, error} -> Repo.rollback(error) + end + end + end) + end + @doc """ Deletes a time_option. - + ## Examples - + iex> delete_time_option(time_option) {:ok, %TimeOption{}} - + iex> delete_time_option(time_option) {:error, %Ecto.Changeset{}} - + """ def delete_time_option(time_option = %TimeOption{}) do Repo.delete(time_option) end + @spec delete_many_time_options([map()]) :: {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} | {:error, String.t} + def delete_many_time_options(to_ids) when is_list(to_ids) do + Repo.transaction(fn -> + for to_id <- to_ids do + time_option = Repo.get(TimeOption, to_id) + + if is_nil(time_option) do + Repo.rollback("Time option do not exist") + else + case Repo.delete(time_option) do + {:ok, time_option} -> time_option + {:error, error} -> Repo.rollback(error) + end + end + end + end) + end + @doc """ Gets the notification preference based from its id """ @@ -282,15 +314,15 @@ defmodule Cadet.Notifications do @doc """ Creates a notification_preference. - + ## Examples - + iex> create_notification_preference(%{field: value}) {:ok, %NotificationPreference{}} - + iex> create_notification_preference(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_notification_preference(attrs \\ %{}) do %NotificationPreference{} @@ -300,15 +332,15 @@ defmodule Cadet.Notifications do @doc """ Updates a notification_preference. - + ## Examples - + iex> update_notification_preference(notification_preference, %{field: new_value}) {:ok, %NotificationPreference{}} - + iex> update_notification_preference(notification_preference, %{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def update_notification_preference(notification_preference = %NotificationPreference{}, attrs) do notification_preference @@ -318,15 +350,15 @@ defmodule Cadet.Notifications do @doc """ Deletes a notification_preference. - + ## Examples - + iex> delete_notification_preference(notification_preference) {:ok, %NotificationPreference{}} - + iex> delete_notification_preference(notification_preference) {:error, %Ecto.Changeset{}} - + """ def delete_notification_preference(notification_preference = %NotificationPreference{}) do Repo.delete(notification_preference) @@ -334,12 +366,12 @@ defmodule Cadet.Notifications do @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_preference changes. - + ## Examples - + iex> change_notification_preference(notification_preference) %Ecto.Changeset{data: %NotificationPreference{}} - + """ def change_notification_preference( notification_preference = %NotificationPreference{}, @@ -350,15 +382,15 @@ defmodule Cadet.Notifications do @doc """ Creates a sent_notification. - + ## Examples - + iex> create_sent_notification(%{field: value}) {:ok, %SentNotification{}} - + iex> create_sent_notification(%{field: bad_value}) {:error, %Ecto.Changeset{}} - + """ def create_sent_notification(course_reg_id, content) do %SentNotification{} @@ -368,12 +400,12 @@ defmodule Cadet.Notifications do @doc """ Returns the list of sent_notifications. - + ## Examples - + iex> list_sent_notifications() [%SentNotification{}, ...] - + """ # def list_sent_notifications do diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index 1a7338614..e15b24cb5 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -3,18 +3,38 @@ defmodule CadetWeb.NewNotificationsController do alias Cadet.Repo alias Cadet.Notifications - alias Cadet.Notifications.{NotificationPreference, TimeOption} + alias Cadet.Notifications.{NotificationPreference, NotificationConfig, TimeOption} # NOTIFICATION CONFIGS def all_noti_configs(conn, %{"course_id" => course_id}) do configs = Notifications.get_notification_configs(course_id) - render(conn, "configs.json", configs: configs) + render(conn, "configs_full.json", configs: configs) end def get_configurable_noti_configs(conn, %{"course_reg_id" => course_reg_id}) do configs = Notifications.get_configurable_notification_configs(course_reg_id) - render(conn, "configs.json", configs: configs) + render(conn, "configs_full.json", configs: configs) + end + + def update_noti_config(conn, %{"noti_config_id" => id, "is_enabled" => is_enabled}) do + config = Notifications.get_notification_config!(id) + + if is_nil(config) do + conn |> put_status(404) |> text("Notification config of given ID not found") + end + + changeset = + config + |> NotificationConfig.changeset(%{is_enabled: is_enabled}) + + case Repo.update(changeset) do + {:ok, res} -> + render(conn, "config.json", config: res) + + {:error, {status, message}} -> + conn |> put_status(status) |> text(message) + end end # NOTIFICATION PREFERENCES @@ -66,15 +86,18 @@ defmodule CadetWeb.NewNotificationsController do render(conn, "time_options.json", %{time_options: time_options}) end - def create_time_option(conn, params) do - changeset = TimeOption.changeset(%TimeOption{}, params) + def create_time_options(conn, params) do + changesets = + params["_json"] + |> Stream.map(fn time_option -> TimeOption.changeset(%TimeOption{}, time_option) end) + |> Enum.to_list() - case Repo.insert(changeset) do + case Notifications.upsert_many_time_options(changesets) do {:ok, res} -> - render(conn, "time_option.json", time_option: res) + render(conn, "time_options.json", time_options: res) - {:error, {status, message}} -> - conn |> put_status(status) |> text(message) + {:error, changeset} -> + conn |> put_status(400) |> text(changeset_error_to_string(changeset)) end end @@ -115,4 +138,24 @@ defmodule CadetWeb.NewNotificationsController do conn |> put_status(status) |> text(message) end end + + def delete_time_options(conn, params) do + # time_option = Repo.get(TimeOption, time_option_id) + + # if is_nil(time_option) do + # conn |> put_status(404) |> text("Time option of given ID not found") + # end + + # case Repo.delete(time_option) do + case Notifications.delete_many_time_options(params["_json"]) do + {:ok, res} -> + render(conn, "time_options.json", time_options: res) + + {:error, message} -> + conn |> put_status(400) |> text(message) + + {:error, changeset} -> + conn |> put_status(400) |> text(changeset_error_to_string(changeset)) + end + end end diff --git a/lib/cadet_web/helpers/controller_helper.ex b/lib/cadet_web/helpers/controller_helper.ex index faa157d07..db4241f36 100644 --- a/lib/cadet_web/helpers/controller_helper.ex +++ b/lib/cadet_web/helpers/controller_helper.ex @@ -42,4 +42,16 @@ defmodule CadetWeb.ControllerHelper do |> Map.merge(Enum.into(extra, %{})) } end + + def changeset_error_to_string(changeset) do + Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) + end) + |> Enum.reduce("", fn {k, v}, acc -> + joined_errors = Enum.join(v, "; ") + "#{acc}#{k}: #{joined_errors}\n" + end) + end end diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 0f6900eb2..4a39652b7 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -173,14 +173,15 @@ defmodule CadetWeb.Router do scope "/v2/notifications/", CadetWeb do get("/config/:course_id", NewNotificationsController, :all_noti_configs) get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) + put("/config/:noti_config_id", NewNotificationsController, :update_noti_config) post("/preference", NewNotificationsController, :create_preference) put("/preference/:noti_pref_id", NewNotificationsController, :update_preference) get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) - post("/options", NewNotificationsController, :create_time_option) + post("/options", NewNotificationsController, :create_time_options) put("/options/:time_option_id", NewNotificationsController, :update_time_option) - delete("/options/:time_option_id", NewNotificationsController, :delete_time_option) + delete("/options", NewNotificationsController, :delete_time_options) end # Other scopes may use custom stacks. diff --git a/lib/cadet_web/views/new_notifications_view.ex b/lib/cadet_web/views/new_notifications_view.ex index 904f169fc..7c297f2d2 100644 --- a/lib/cadet_web/views/new_notifications_view.ex +++ b/lib/cadet_web/views/new_notifications_view.ex @@ -3,6 +3,7 @@ defmodule CadetWeb.NewNotificationsView do require IEx + # Notification Type def render("noti_types.json", %{noti_types: noti_types}) do render_many(noti_types, CadetWeb.NewNotificationsView, "noti_type.json", as: :noti_type) end @@ -11,11 +12,12 @@ defmodule CadetWeb.NewNotificationsView do render_notification_type(noti_type) end - def render("configs.json", %{configs: configs}) do - render_many(configs, CadetWeb.NewNotificationsView, "config.json", as: :config) + # Notification Config + def render("configs_full.json", %{configs: configs}) do + render_many(configs, CadetWeb.NewNotificationsView, "config_full.json", as: :config) end - def render("config.json", %{config: config}) do + def render("config_full.json", %{config: config}) do transform_map_for_view(config, %{ id: :id, isEnabled: :is_enabled, @@ -33,6 +35,14 @@ defmodule CadetWeb.NewNotificationsView do }) end + def render("config.json", %{config: config}) do + transform_map_for_view(config, %{ + id: :id, + isEnabled: :is_enabled + }) + end + + # Notification Preference def render("noti_pref.json", %{noti_pref: noti_pref}) do transform_map_for_view(noti_pref, %{ id: :id, @@ -41,6 +51,7 @@ defmodule CadetWeb.NewNotificationsView do }) end + # Time Options def render("time_options.json", %{time_options: time_options}) do render_many(time_options, CadetWeb.NewNotificationsView, "time_option.json", as: :time_option) end @@ -53,6 +64,7 @@ defmodule CadetWeb.NewNotificationsView do }) end + # Helpers defp render_notification_type(noti_type) do case noti_type do nil -> From a0b8eef5a95bb0f8d644a3c78ab9505991911d91 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Tue, 4 Apr 2023 16:42:44 +0800 Subject: [PATCH 53/80] update notifications query to return a single preference * Query returns an array of preferences per config due to LEFT OUTER JOIN, the change ensures either nil or the first preference is returned * It is guaranteed there is maximally one preference in the array due to the unique constraint in the new migration --- lib/cadet/notifications.ex | 8 +++++--- lib/cadet/notifications/notification_preference.ex | 3 ++- .../controllers/new_notifications_controller.ex | 4 ++-- lib/cadet_web/views/new_notifications_view.ex | 11 ++++++++--- ...add_unique_constraint_notification_preferences.exs | 7 +++++++ 5 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 15f640b87..fb222d712 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -105,7 +105,7 @@ defmodule Cadet.Notifications do where: ntype.for_staff == ^is_staff and n.course_id == ^cr.course_id and - p.course_reg_id == ^cr.id + (p.course_reg_id == ^cr.id or is_nil(p.course_reg_id)) ) query @@ -234,7 +234,8 @@ defmodule Cadet.Notifications do |> Repo.insert() end - @spec upsert_many_time_options([map()]) :: {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} + @spec upsert_many_time_options([map()]) :: + {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} def upsert_many_time_options(time_options) when is_list(time_options) do Repo.transaction(fn -> for to <- time_options do @@ -262,7 +263,8 @@ defmodule Cadet.Notifications do Repo.delete(time_option) end - @spec delete_many_time_options([map()]) :: {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} | {:error, String.t} + @spec delete_many_time_options([map()]) :: + {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} | {:error, String.t()} def delete_many_time_options(to_ids) when is_list(to_ids) do Repo.transaction(fn -> for to_id <- to_ids do diff --git a/lib/cadet/notifications/notification_preference.ex b/lib/cadet/notifications/notification_preference.ex index aec18aa5e..404f1f250 100644 --- a/lib/cadet/notifications/notification_preference.ex +++ b/lib/cadet/notifications/notification_preference.ex @@ -20,8 +20,9 @@ defmodule Cadet.Notifications.NotificationPreference do @doc false def changeset(notification_preference, attrs) do notification_preference - |> cast(attrs, [:is_enabled, :notification_config_id, :course_reg_id]) + |> cast(attrs, [:is_enabled, :notification_config_id, :course_reg_id, :time_option_id]) |> validate_required([:notification_config_id, :course_reg_id]) + |> unique_constraint(:unique_course_reg_and_config, name: :single_preference_per_config) |> prevent_nil_is_enabled() end diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index e15b24cb5..694fafee2 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -47,8 +47,8 @@ defmodule CadetWeb.NewNotificationsController do pref = Notifications.get_notification_preference!(res.id) render(conn, "noti_pref.json", noti_pref: pref) - {:error, {status, message}} -> - conn |> put_status(status) |> text(message) + {:error, changeset} -> + conn |> put_status(400) |> text(changeset_error_to_string(changeset)) end end diff --git a/lib/cadet_web/views/new_notifications_view.ex b/lib/cadet_web/views/new_notifications_view.ex index 7c297f2d2..32fa04343 100644 --- a/lib/cadet_web/views/new_notifications_view.ex +++ b/lib/cadet_web/views/new_notifications_view.ex @@ -24,7 +24,7 @@ defmodule CadetWeb.NewNotificationsView do course: &render_course(&1.course), notificationType: &render_notification_type(&1.notification_type), assessmentConfig: &render_assessment_config(&1.assessment_config), - notificationPreference: &render_many_notification_preferences(&1.notification_preferences), + notificationPreference: &render_first_notification_preferences(&1.notification_preferences), timeOptions: &render_many( &1.time_options, @@ -80,7 +80,8 @@ defmodule CadetWeb.NewNotificationsView do end end - defp render_many_notification_preferences(noti_prefs) do + # query returns an array but there should be max 1 result + defp render_first_notification_preferences(noti_prefs) do case noti_prefs do nil -> nil @@ -89,7 +90,11 @@ defmodule CadetWeb.NewNotificationsView do nil _ -> - render_many(noti_prefs, CadetWeb.NewNotificationsView, "noti_pref.json", as: :noti_pref) + if Enum.empty?(noti_prefs) do + nil + else + render("noti_pref.json", %{noti_pref: Enum.at(noti_prefs, 0)}) + end end end diff --git a/priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs b/priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs new file mode 100644 index 000000000..4a7c9c542 --- /dev/null +++ b/priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs @@ -0,0 +1,7 @@ +defmodule Cadet.Repo.Migrations.AddUniqueConstraintNotificationPreferences do + use Ecto.Migration + + def change do + create unique_index(:notification_preferences, [:notification_config_id, :course_reg_id], name: :single_preference_per_config) + end +end From 059d969aacf9386de2dd73c88462f913ea83fc40 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:11:04 +0800 Subject: [PATCH 54/80] Update endpoint to batch update notification configs instead --- lib/cadet/notifications.ex | 14 ++++++++++- .../new_notifications_controller.ex | 23 +++++++----------- lib/cadet_web/router.ex | 2 +- lib/cadet_web/views/new_notifications_view.ex | 24 ++++++++++++++----- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index fb222d712..01a874068 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -137,6 +137,18 @@ defmodule Cadet.Notifications do |> Repo.update() end + @spec update_many_noti_configs([map()]) :: {:ok, [NotificationConfig.t()]} | {:error, {:bad_request, String.t()}} + def update_many_noti_configs(noti_configs) when is_list(noti_configs) do + Repo.transaction(fn -> + for noti_config <- noti_configs do + case Repo.update(noti_config) do + {:ok, res} -> res + {:error, error} -> Repo.rollback(error) + end + end + end) + end + @doc """ Returns an `%Ecto.Changeset{}` for tracking notification_config changes. @@ -239,7 +251,7 @@ defmodule Cadet.Notifications do def upsert_many_time_options(time_options) when is_list(time_options) do Repo.transaction(fn -> for to <- time_options do - case Repo.insert(to) do + case Repo.insert(to, on_conflict: :nothing) do {:ok, time_option} -> time_option {:error, error} -> Repo.rollback(error) end diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index 694fafee2..3f2e84cba 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -17,23 +17,18 @@ defmodule CadetWeb.NewNotificationsController do render(conn, "configs_full.json", configs: configs) end - def update_noti_config(conn, %{"noti_config_id" => id, "is_enabled" => is_enabled}) do - config = Notifications.get_notification_config!(id) - - if is_nil(config) do - conn |> put_status(404) |> text("Notification config of given ID not found") - end - - changeset = - config - |> NotificationConfig.changeset(%{is_enabled: is_enabled}) + def update_noti_configs(conn, params) do + changesets = + params["_json"] + |> Stream.map(fn noti_config -> NotificationConfig.changeset(%NotificationConfig{id: noti_config["id"]}, noti_config) end) + |> Enum.to_list() - case Repo.update(changeset) do + case Notifications.update_many_noti_configs(changesets) do {:ok, res} -> - render(conn, "config.json", config: res) + render(conn, "configs_full.json", configs: res) - {:error, {status, message}} -> - conn |> put_status(status) |> text(message) + {:error, changeset} -> + conn |> put_status(400) |> text(changeset_error_to_string(changeset)) end end diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 4a39652b7..d4fe530be 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -173,7 +173,7 @@ defmodule CadetWeb.Router do scope "/v2/notifications/", CadetWeb do get("/config/:course_id", NewNotificationsController, :all_noti_configs) get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) - put("/config/:noti_config_id", NewNotificationsController, :update_noti_config) + put("/config/", NewNotificationsController, :update_noti_configs) post("/preference", NewNotificationsController, :create_preference) put("/preference/:noti_pref_id", NewNotificationsController, :update_preference) diff --git a/lib/cadet_web/views/new_notifications_view.ex b/lib/cadet_web/views/new_notifications_view.ex index 32fa04343..a2166aa8e 100644 --- a/lib/cadet_web/views/new_notifications_view.ex +++ b/lib/cadet_web/views/new_notifications_view.ex @@ -26,11 +26,9 @@ defmodule CadetWeb.NewNotificationsView do assessmentConfig: &render_assessment_config(&1.assessment_config), notificationPreference: &render_first_notification_preferences(&1.notification_preferences), timeOptions: - &render_many( - &1.time_options, - CadetWeb.NewNotificationsView, - "time_option.json", - as: :time_option + &render( + "time_options.json", + %{time_options: &1.time_options} ) }) end @@ -53,7 +51,12 @@ defmodule CadetWeb.NewNotificationsView do # Time Options def render("time_options.json", %{time_options: time_options}) do - render_many(time_options, CadetWeb.NewNotificationsView, "time_option.json", as: :time_option) + case time_options do + %Ecto.Association.NotLoaded{} -> + nil + _ -> + render_many(time_options, CadetWeb.NewNotificationsView, "time_option.json", as: :time_option) + end end def render("time_option.json", %{time_option: time_option}) do @@ -70,6 +73,9 @@ defmodule CadetWeb.NewNotificationsView do nil -> nil + %Ecto.Association.NotLoaded{} -> + nil + _ -> transform_map_for_view(noti_type, %{ id: :id, @@ -103,6 +109,9 @@ defmodule CadetWeb.NewNotificationsView do nil -> nil + %Ecto.Association.NotLoaded{} -> + nil + _ -> transform_map_for_view(ass_config, %{ id: :id, @@ -116,6 +125,9 @@ defmodule CadetWeb.NewNotificationsView do nil -> nil + %Ecto.Association.NotLoaded{} -> + nil + _ -> transform_map_for_view(course, %{ id: :id, From 2cc2f35447e2d57e1aa7c73e841916d5f48ef071 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Thu, 6 Apr 2023 04:13:48 +0800 Subject: [PATCH 55/80] Fix Notifications Endpoints - Add put upsert time options endpoints - Fix changeset issues with upsert noti config endpoints --- lib/cadet/notifications.ex | 8 ++++++-- .../controllers/new_notifications_controller.ex | 17 ++++++++++++++--- lib/cadet_web/router.ex | 4 ++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 01a874068..c35148a61 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -137,7 +137,8 @@ defmodule Cadet.Notifications do |> Repo.update() end - @spec update_many_noti_configs([map()]) :: {:ok, [NotificationConfig.t()]} | {:error, {:bad_request, String.t()}} + @spec update_many_noti_configs([map()]) :: + {:ok, [NotificationConfig.t()]} | {:error, {:bad_request, String.t()}} def update_many_noti_configs(noti_configs) when is_list(noti_configs) do Repo.transaction(fn -> for noti_config <- noti_configs do @@ -251,7 +252,10 @@ defmodule Cadet.Notifications do def upsert_many_time_options(time_options) when is_list(time_options) do Repo.transaction(fn -> for to <- time_options do - case Repo.insert(to, on_conflict: :nothing) do + case Repo.insert(to, + on_conflict: {:replace, [:is_default]}, + conflict_target: [:minutes, :notification_config_id] + ) do {:ok, time_option} -> time_option {:error, error} -> Repo.rollback(error) end diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index 3f2e84cba..fd8d6c758 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -20,7 +20,11 @@ defmodule CadetWeb.NewNotificationsController do def update_noti_configs(conn, params) do changesets = params["_json"] - |> Stream.map(fn noti_config -> NotificationConfig.changeset(%NotificationConfig{id: noti_config["id"]}, noti_config) end) + |> snake_casify_string_keys_recursive() + |> Stream.map(fn noti_config -> + config = Repo.get(NotificationConfig, noti_config["id"]) + NotificationConfig.changeset(config, noti_config) + end) |> Enum.to_list() case Notifications.update_many_noti_configs(changesets) do @@ -81,10 +85,17 @@ defmodule CadetWeb.NewNotificationsController do render(conn, "time_options.json", %{time_options: time_options}) end - def create_time_options(conn, params) do + def upsert_time_options(conn, params) do changesets = params["_json"] - |> Stream.map(fn time_option -> TimeOption.changeset(%TimeOption{}, time_option) end) + |> snake_casify_string_keys_recursive() + |> Stream.map(fn time_option -> + if time_option["id"] < 0 do + Map.delete(time_option, "id") + end + + TimeOption.changeset(%TimeOption{}, time_option) + end) |> Enum.to_list() case Notifications.upsert_many_time_options(changesets) do diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index d4fe530be..21a8fe418 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -179,8 +179,8 @@ defmodule CadetWeb.Router do put("/preference/:noti_pref_id", NewNotificationsController, :update_preference) get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) - post("/options", NewNotificationsController, :create_time_options) - put("/options/:time_option_id", NewNotificationsController, :update_time_option) + post("/options", NewNotificationsController, :upsert_time_options) + put("/options", NewNotificationsController, :upsert_time_options) delete("/options", NewNotificationsController, :delete_time_options) end From 2880a3a177efcace0c81fd872ca80b232195dfde Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Tue, 30 May 2023 01:06:46 -0700 Subject: [PATCH 56/80] Add upsert endpoint for notification preferences --- lib/cadet/notifications.ex | 14 +++++++++++ .../new_notifications_controller.ex | 23 +++++++++++++++++++ lib/cadet_web/router.ex | 3 +-- lib/cadet_web/views/new_notifications_view.ex | 9 +++++++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index c35148a61..72bae0328 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -263,6 +263,20 @@ defmodule Cadet.Notifications do end) end + def upsert_many_noti_preferences(noti_prefs) when is_list(noti_prefs) do + Repo.transaction(fn -> + for np <- noti_prefs do + case Repo.insert(np, + on_conflict: {:replace, [:is_enabled, :time_option_id]}, + conflict_target: [:course_reg_id, :notification_config_id] + ) do + {:ok, noti_pref} -> noti_pref + {:error, error} -> Repo.rollback(error) + end + end + end) + end + @doc """ Deletes a time_option. diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index fd8d6c758..1982b8b16 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -107,6 +107,28 @@ defmodule CadetWeb.NewNotificationsController do end end + def upsert_noti_preferences(conn, params) do + changesets = + params["_json"] + |> snake_casify_string_keys_recursive() + |> Stream.map(fn noti_pref -> + if noti_pref["id"] < 0 do + Map.delete(noti_pref, "id") + end + + NotificationPreference.changeset(%NotificationPreference{}, noti_pref) + end) + |> Enum.to_list() + + case Notifications.upsert_many_noti_preferences(changesets) do + {:ok, res} -> + render(conn, "noti_prefs.json", noti_prefs: res) + + {:error, changeset} -> + conn |> put_status(400) |> text(changeset_error_to_string(changeset)) + end + end + # Only allow updating of `is_default` # should delete and create a new entry for other fields def update_time_option(conn, %{"time_option_id" => time_option_id, "is_default" => is_default}) do @@ -153,6 +175,7 @@ defmodule CadetWeb.NewNotificationsController do # end # case Repo.delete(time_option) do + # JUNYI AND SANTOSH: NOT DEFINED??? case Notifications.delete_many_time_options(params["_json"]) do {:ok, res} -> render(conn, "time_options.json", time_options: res) diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 21a8fe418..97d98bcc7 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -175,8 +175,7 @@ defmodule CadetWeb.Router do get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) put("/config/", NewNotificationsController, :update_noti_configs) - post("/preference", NewNotificationsController, :create_preference) - put("/preference/:noti_pref_id", NewNotificationsController, :update_preference) + put("/preferences", NewNotificationsController, :upsert_noti_preferences) get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) post("/options", NewNotificationsController, :upsert_time_options) diff --git a/lib/cadet_web/views/new_notifications_view.ex b/lib/cadet_web/views/new_notifications_view.ex index a2166aa8e..e5ef34aa8 100644 --- a/lib/cadet_web/views/new_notifications_view.ex +++ b/lib/cadet_web/views/new_notifications_view.ex @@ -49,13 +49,20 @@ defmodule CadetWeb.NewNotificationsView do }) end + def render("noti_prefs.json", %{noti_prefs: noti_prefs}) do + render_many(noti_prefs, CadetWeb.NewNotificationsView, "noti_pref.json", as: :noti_pref) + end + # Time Options def render("time_options.json", %{time_options: time_options}) do case time_options do %Ecto.Association.NotLoaded{} -> nil + _ -> - render_many(time_options, CadetWeb.NewNotificationsView, "time_option.json", as: :time_option) + render_many(time_options, CadetWeb.NewNotificationsView, "time_option.json", + as: :time_option + ) end end From e8a28d61cdb8f97d08d4d3d75c2bbc1c899fc9e6 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Wed, 14 Jun 2023 01:20:48 -0700 Subject: [PATCH 57/80] Fix linting --- ...82921_add_unique_constraint_notification_preferences.exs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs b/priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs index 4a7c9c542..6e141e5b2 100644 --- a/priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs +++ b/priv/repo/migrations/20230404082921_add_unique_constraint_notification_preferences.exs @@ -2,6 +2,10 @@ defmodule Cadet.Repo.Migrations.AddUniqueConstraintNotificationPreferences do use Ecto.Migration def change do - create unique_index(:notification_preferences, [:notification_config_id, :course_reg_id], name: :single_preference_per_config) + create( + unique_index(:notification_preferences, [:notification_config_id, :course_reg_id], + name: :single_preference_per_config + ) + ) end end From 5cc0439dd545155e0ec40d207b2b9b5d5ec89a04 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Wed, 14 Jun 2023 01:25:45 -0700 Subject: [PATCH 58/80] Fix linting --- lib/cadet_web/helpers/controller_helper.ex | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/cadet_web/helpers/controller_helper.ex b/lib/cadet_web/helpers/controller_helper.ex index db4241f36..b4081c1a5 100644 --- a/lib/cadet_web/helpers/controller_helper.ex +++ b/lib/cadet_web/helpers/controller_helper.ex @@ -44,11 +44,14 @@ defmodule CadetWeb.ControllerHelper do end def changeset_error_to_string(changeset) do - Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> - Enum.reduce(opts, msg, fn {key, value}, acc -> - String.replace(acc, "%{#{key}}", to_string(value)) + errors = + Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) end) - end) + + errors |> Enum.reduce("", fn {k, v}, acc -> joined_errors = Enum.join(v, "; ") "#{acc}#{k}: #{joined_errors}\n" From 62065389c4414f53e52ea0542c68db90361635ea Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Wed, 14 Jun 2023 01:30:26 -0700 Subject: [PATCH 59/80] Fix linting --- lib/cadet_web/controllers/new_notifications_controller.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index 1982b8b16..dc2af0684 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -1,8 +1,7 @@ defmodule CadetWeb.NewNotificationsController do use CadetWeb, :controller - alias Cadet.Repo - alias Cadet.Notifications + alias Cadet.{Repo, Notifications} alias Cadet.Notifications.{NotificationPreference, NotificationConfig, TimeOption} # NOTIFICATION CONFIGS From 45f3e1cbae4878c61ae356e653aafaff69587e1b Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Wed, 14 Jun 2023 01:56:53 -0700 Subject: [PATCH 60/80] Fix Notification Type tests --- lib/cadet/notifications.ex | 2 -- .../notifications/notification_type_test.exs | 25 ++++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 72bae0328..f901b8546 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -14,8 +14,6 @@ defmodule Cadet.Notifications do NotificationPreference } - alias Cadet.Accounts.Role - @doc """ Gets a single notification_type. diff --git a/test/cadet/notifications/notification_type_test.exs b/test/cadet/notifications/notification_type_test.exs index 547795521..6bf63b0f7 100644 --- a/test/cadet/notifications/notification_type_test.exs +++ b/test/cadet/notifications/notification_type_test.exs @@ -10,7 +10,8 @@ defmodule Cadet.Notifications.NotificationTypeTest do name: "Notification Type 1", template_file_name: "template_file_1", is_enabled: true, - is_autopopulated: true + is_autopopulated: true, + for_staff: true }) {:ok, _noti_type1} = Repo.insert(changeset) @@ -25,7 +26,8 @@ defmodule Cadet.Notifications.NotificationTypeTest do name: "Notification Type 2", template_file_name: "template_file_2", is_enabled: false, - is_autopopulated: true + is_autopopulated: true, + for_staff: true }, :valid ) @@ -36,7 +38,8 @@ defmodule Cadet.Notifications.NotificationTypeTest do %{ template_file_name: "template_file_2", is_enabled: false, - is_autopopulated: true + is_autopopulated: true, + for_staff: true }, :invalid ) @@ -47,6 +50,19 @@ defmodule Cadet.Notifications.NotificationTypeTest do %{ name: "Notification Type 2", is_enabled: false, + is_autopopulated: true, + for_staff: false + }, + :invalid + ) + end + + test "invalid changesets missing for_staff" do + assert_changeset( + %{ + name: "Notification Type 2", + template_file_name: "template_file_2", + is_enabled: false, is_autopopulated: true }, :invalid @@ -70,7 +86,8 @@ defmodule Cadet.Notifications.NotificationTypeTest do %{ name: "Notification Type 0", is_enabled: nil, - is_autopopulated: true + is_autopopulated: true, + for_staff: true }, :invalid ) From 5b50c4724e79c27ca17685e075c102834c6193cd Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Thu, 15 Jun 2023 01:11:55 +0800 Subject: [PATCH 61/80] Fix spec issues for notifications --- lib/cadet/notifications.ex | 8 ++++---- lib/cadet_web/controllers/new_notifications_controller.ex | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index f901b8546..16971dd96 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -136,7 +136,7 @@ defmodule Cadet.Notifications do end @spec update_many_noti_configs([map()]) :: - {:ok, [NotificationConfig.t()]} | {:error, {:bad_request, String.t()}} + {:ok, [NotificationConfig.t()]} | {:error, Ecto.Changeset.t} def update_many_noti_configs(noti_configs) when is_list(noti_configs) do Repo.transaction(fn -> for noti_config <- noti_configs do @@ -246,7 +246,7 @@ defmodule Cadet.Notifications do end @spec upsert_many_time_options([map()]) :: - {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} + {:ok, [TimeOption.t()]} | {:error, Ecto.Changeset.t} def upsert_many_time_options(time_options) when is_list(time_options) do Repo.transaction(fn -> for to <- time_options do @@ -292,7 +292,7 @@ defmodule Cadet.Notifications do end @spec delete_many_time_options([map()]) :: - {:ok, [TimeOption.t()]} | {:error, {:bad_request, String.t()}} | {:error, String.t()} + {:ok, [TimeOption.t()]} | {:delete_error, Ecto.Changeset.t} | {:error, String.t()} def delete_many_time_options(to_ids) when is_list(to_ids) do Repo.transaction(fn -> for to_id <- to_ids do @@ -303,7 +303,7 @@ defmodule Cadet.Notifications do else case Repo.delete(time_option) do {:ok, time_option} -> time_option - {:error, error} -> Repo.rollback(error) + {:delete_error, error} -> Repo.rollback(error) end end end diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index dc2af0684..e7467760d 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -182,7 +182,7 @@ defmodule CadetWeb.NewNotificationsController do {:error, message} -> conn |> put_status(400) |> text(message) - {:error, changeset} -> + {:delete_error, changeset} -> conn |> put_status(400) |> text(changeset_error_to_string(changeset)) end end From b620cd336d8bfc9e2668dbd2e43cfa7d7a433f85 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Fri, 16 Jun 2023 00:14:04 -0700 Subject: [PATCH 62/80] FIx linting --- lib/cadet/notifications.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 16971dd96..ea4c1fe1a 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -136,7 +136,7 @@ defmodule Cadet.Notifications do end @spec update_many_noti_configs([map()]) :: - {:ok, [NotificationConfig.t()]} | {:error, Ecto.Changeset.t} + {:ok, [NotificationConfig.t()]} | {:error, Ecto.Changeset.t()} def update_many_noti_configs(noti_configs) when is_list(noti_configs) do Repo.transaction(fn -> for noti_config <- noti_configs do @@ -246,7 +246,7 @@ defmodule Cadet.Notifications do end @spec upsert_many_time_options([map()]) :: - {:ok, [TimeOption.t()]} | {:error, Ecto.Changeset.t} + {:ok, [TimeOption.t()]} | {:error, Ecto.Changeset.t()} def upsert_many_time_options(time_options) when is_list(time_options) do Repo.transaction(fn -> for to <- time_options do @@ -292,7 +292,7 @@ defmodule Cadet.Notifications do end @spec delete_many_time_options([map()]) :: - {:ok, [TimeOption.t()]} | {:delete_error, Ecto.Changeset.t} | {:error, String.t()} + {:ok, [TimeOption.t()]} | {:delete_error, Ecto.Changeset.t()} | {:error, String.t()} def delete_many_time_options(to_ids) when is_list(to_ids) do Repo.transaction(fn -> for to_id <- to_ids do From 1ff2fb1d023c7a364f54e2fd3d5478ce392a5081 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Fri, 16 Jun 2023 01:06:19 -0700 Subject: [PATCH 63/80] Fix avenger backlog tests --- lib/cadet/notifications.ex | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index ea4c1fe1a..62b1cd726 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -62,7 +62,17 @@ defmodule Cadet.Notifications do where(query, [c], c.assessment_config_id == ^assconfig_id) end - Repo.one!(query) + config = Repo.one(query) + + if config != nil do + config + else + IO.puts( + "No NotificationConfig found for Course #{course_id} and NotificationType #{notification_type_id}" + ) + + nil + end end def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) From bb467710ea770398d2963c84e2d6027da81168ac Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Fri, 16 Jun 2023 01:21:47 -0700 Subject: [PATCH 64/80] Fix Type Checking --- lib/cadet/notifications.ex | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 62b1cd726..64aa90280 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -145,8 +145,6 @@ defmodule Cadet.Notifications do |> Repo.update() end - @spec update_many_noti_configs([map()]) :: - {:ok, [NotificationConfig.t()]} | {:error, Ecto.Changeset.t()} def update_many_noti_configs(noti_configs) when is_list(noti_configs) do Repo.transaction(fn -> for noti_config <- noti_configs do @@ -255,8 +253,6 @@ defmodule Cadet.Notifications do |> Repo.insert() end - @spec upsert_many_time_options([map()]) :: - {:ok, [TimeOption.t()]} | {:error, Ecto.Changeset.t()} def upsert_many_time_options(time_options) when is_list(time_options) do Repo.transaction(fn -> for to <- time_options do @@ -301,8 +297,6 @@ defmodule Cadet.Notifications do Repo.delete(time_option) end - @spec delete_many_time_options([map()]) :: - {:ok, [TimeOption.t()]} | {:delete_error, Ecto.Changeset.t()} | {:error, String.t()} def delete_many_time_options(to_ids) when is_list(to_ids) do Repo.transaction(fn -> for to_id <- to_ids do From 95206d48512edfec03fdb581687d84a18c3e9066 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 17 Jun 2023 02:50:43 +0800 Subject: [PATCH 65/80] Add tests for notifications controller * Removed extra unused route --- lib/cadet/notifications.ex | 58 +++--- .../new_notifications_controller.ex | 7 +- lib/cadet_web/router.ex | 1 - .../new_notifications_controller_test.exs | 189 ++++++++++++++++++ .../notifications/time_option_factory.ex | 2 +- 5 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 test/cadet_web/controllers/new_notifications_controller_test.exs diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 64aa90280..677602a13 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -98,33 +98,37 @@ defmodule Cadet.Notifications do """ def get_configurable_notification_configs(cr_id) do cr = Repo.get(Cadet.Accounts.CourseRegistration, cr_id) - is_staff = cr.role == :staff - - query = - from(n in Cadet.Notifications.NotificationConfig, - join: ntype in Cadet.Notifications.NotificationType, - on: n.notification_type_id == ntype.id, - join: c in Cadet.Courses.Course, - on: n.course_id == c.id, - left_join: ac in Cadet.Courses.AssessmentConfig, - on: n.assessment_config_id == ac.id, - left_join: p in Cadet.Notifications.NotificationPreference, - on: p.notification_config_id == n.id, - where: - ntype.for_staff == ^is_staff and - n.course_id == ^cr.course_id and - (p.course_reg_id == ^cr.id or is_nil(p.course_reg_id)) - ) - - query - |> Repo.all() - |> Repo.preload([ - :notification_type, - :course, - :assessment_config, - :time_options, - :notification_preferences - ]) + case cr do + nil -> nil + _ -> + is_staff = cr.role == :staff + + query = + from(n in Cadet.Notifications.NotificationConfig, + join: ntype in Cadet.Notifications.NotificationType, + on: n.notification_type_id == ntype.id, + join: c in Cadet.Courses.Course, + on: n.course_id == c.id, + left_join: ac in Cadet.Courses.AssessmentConfig, + on: n.assessment_config_id == ac.id, + left_join: p in Cadet.Notifications.NotificationPreference, + on: p.notification_config_id == n.id, + where: + ntype.for_staff == ^is_staff and + n.course_id == ^cr.course_id and + (p.course_reg_id == ^cr.id or is_nil(p.course_reg_id)) + ) + + query + |> Repo.all() + |> Repo.preload([ + :notification_type, + :course, + :assessment_config, + :time_options, + :notification_preferences + ]) + end end @doc """ diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index e7467760d..7846200f2 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -13,7 +13,12 @@ defmodule CadetWeb.NewNotificationsController do def get_configurable_noti_configs(conn, %{"course_reg_id" => course_reg_id}) do configs = Notifications.get_configurable_notification_configs(course_reg_id) - render(conn, "configs_full.json", configs: configs) + + case configs do + nil -> conn |> put_status(400) |> text("course_reg_id does not exist") + _ -> render(conn, "configs_full.json", configs: configs) + end + end def update_noti_configs(conn, params) do diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 97d98bcc7..437f75d3b 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -178,7 +178,6 @@ defmodule CadetWeb.Router do put("/preferences", NewNotificationsController, :upsert_noti_preferences) get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) - post("/options", NewNotificationsController, :upsert_time_options) put("/options", NewNotificationsController, :upsert_time_options) delete("/options", NewNotificationsController, :delete_time_options) end diff --git a/test/cadet_web/controllers/new_notifications_controller_test.exs b/test/cadet_web/controllers/new_notifications_controller_test.exs new file mode 100644 index 000000000..d49cb9317 --- /dev/null +++ b/test/cadet_web/controllers/new_notifications_controller_test.exs @@ -0,0 +1,189 @@ +# Results of tests depends on the number of notifications implemented in Source Academy, +# test expected values have to be updated as more notification types are introduced +defmodule CadetWeb.AssessmentsControllerTest do + use CadetWeb.ConnCase + + import Ecto.Query, warn: false + + alias Cadet.Notifications.{NotificationConfig, NotificationType} + + setup do + course = insert(:course) + assessment_config = insert(:assessment_config, %{course: course}) + assessment = insert(:assessment, %{is_published: true, course: course, config: assessment_config}) + avenger = insert(:course_registration, %{role: :staff, course: course}) + student = insert(:course_registration, %{role: :student, course: course}) + submission = insert(:submission, %{student: student, assessment: assessment}) + + Ecto.Adapters.SQL.Sandbox.checkout(Cadet.Repo) + + course_noticonfig_query = from( + nc in NotificationConfig, + join: ntype in NotificationType, + on: nc.notification_type_id == ntype.id, + where: nc.course_id == ^course.id and is_nil(nc.assessment_config_id) and ntype.for_staff == true, + limit: 1 + ) + course_noticonfig = Cadet.Repo.one(course_noticonfig_query) + + # insert a notification preference for the avenger + avenger_preference = insert(:notification_preference, %{ + notification_config: course_noticonfig, + course_reg: avenger, + is_enabled: false + }) + + # insert 2 time options for the notification config + time_options = insert_list(2, :time_option, %{notification_config: course_noticonfig}) + + {:ok, + %{ + course: course, + assessment_config: assessment_config, + assessment: assessment, + avenger: avenger, + student: student, + submission: submission, + course_noticonfig: course_noticonfig, + avenger_preference: avenger_preference, + time_options: time_options + }} + end + + describe "GET /v2/notifications/config/:course_id" do + test "200 suceeds", %{course: course, conn: conn} do + + conn = get(conn, "/v2/notifications/config/#{course.id}") + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 2 + end + end + + describe "GET /v2/notifications/config/user/:course_reg_id" do + test "200 succeeds for avenger", %{avenger: avenger, conn: conn} do + conn = get(conn, "/v2/notifications/config/user/#{avenger.id}") + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 2 + end + + test "200 succeeds for student", %{student: student, conn: conn} do + conn = get(conn, "/v2/notifications/config/user/#{student.id}") + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 0 + end + + test "400 fails, user does not exist", %{conn: conn} do + conn = get(conn, "/v2/notifications/config/user/-1") + assert response(conn, 400) + end + end + + describe "PUT /v2/notifications/config" do + test "200 succeeds", %{course_noticonfig: course_noticonfig, conn: conn} do + conn = put(conn, "/v2/notifications/config", %{ + "_json" => [%{:id => course_noticonfig.id, :isEnabled => true}] + }) + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 1 + assert List.first(result)["isEnabled"] == true + end + end + + describe "PUT /v2/notifications/preferences" do + test "200 succeeds, update", %{ + avenger_preference: avenger_preference, + avenger: avenger, + course_noticonfig: course_noticonfig, + conn: conn + } do + conn = put(conn, "/v2/notifications/preferences", %{ + "_json" => [%{:id => avenger_preference.id, :courseRegId => avenger.id, :notificationConfigId => course_noticonfig.id, :isEnabled => true}] + }) + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 1 + assert List.first(result)["isEnabled"] == true + end + end + + describe "GET /options/config/:noti_config_id" do + test "200 succeeds", %{course_noticonfig: course_noticonfig, time_options: time_options, conn: conn} do + conn = get(conn, "/v2/notifications/options/config/#{course_noticonfig.id}") + result = Jason.decode!(response(conn, 200)) + + assert length(result) == length(time_options) + for {retrieved_to, to} <- Enum.zip(result, time_options) do + assert retrieved_to["minutes"] == to.minutes + end + end + + test "200 succeeds, empty array as notification config record not found", %{conn: conn} do + conn = get(conn, "/v2/notifications/options/config/-1") + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 0 + end + end + + # Due to unique constraint on the column 'minutes', + # test cases may fail if the generator produces the same number + describe "PUT /v2/notifications/options" do + test "200 succeeds, update", %{ + time_options: time_options, + course_noticonfig: course_noticonfig, + conn: conn + } do + time_option = List.first(time_options) + new_minutes = :rand.uniform(200) + conn = put(conn, "/v2/notifications/options", %{ + "_json" => [%{:id => time_option.id, :notificationConfigId => course_noticonfig.id, :minutes => new_minutes}] + }) + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 1 + assert List.first(result)["minutes"] == new_minutes + end + + test "200 succeeds, insert", %{ + course_noticonfig: course_noticonfig, + conn: conn + } do + minutes = :rand.uniform(500) + conn = put(conn, "/v2/notifications/options", %{ + "_json" => [%{:notificationConfigId => course_noticonfig.id, :minutes => minutes}] + }) + result = Jason.decode!(response(conn, 200)) + + assert length(result) == 1 + assert List.first(result)["minutes"] == minutes + end + end + + describe "DELETE /v2/notifications/options" do + test "200 succeeds", %{ + time_options: time_options, + conn: conn + } do + time_option = List.first(time_options) + conn = delete(conn, "/v2/notifications/options", %{ + "_json" => [time_option.id] + }) + + assert response(conn, 200) + end + end + + test "400 fails, no such time option", %{ + conn: conn + } do + conn = delete(conn, "/v2/notifications/options", %{ + "_json" => [-1] + }) + assert response(conn, 400) + + end +end diff --git a/test/factories/notifications/time_option_factory.ex b/test/factories/notifications/time_option_factory.ex index d5aa1c898..c2c677cef 100644 --- a/test/factories/notifications/time_option_factory.ex +++ b/test/factories/notifications/time_option_factory.ex @@ -10,7 +10,7 @@ defmodule Cadet.Notifications.TimeOptionFactory do def time_option_factory do %TimeOption{ is_default: false, - minutes: 0, + minutes: :rand.uniform(500), notification_config: build(:notification_config) } end From eedaf8c9852d09bc55ed4cb1d66e6d5731d74c2c Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 17 Jun 2023 02:52:45 +0800 Subject: [PATCH 66/80] Fix credo errors --- .../controllers/new_notifications_controller_test.exs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/cadet_web/controllers/new_notifications_controller_test.exs b/test/cadet_web/controllers/new_notifications_controller_test.exs index d49cb9317..bef23665f 100644 --- a/test/cadet_web/controllers/new_notifications_controller_test.exs +++ b/test/cadet_web/controllers/new_notifications_controller_test.exs @@ -10,7 +10,11 @@ defmodule CadetWeb.AssessmentsControllerTest do setup do course = insert(:course) assessment_config = insert(:assessment_config, %{course: course}) - assessment = insert(:assessment, %{is_published: true, course: course, config: assessment_config}) + assessment = insert(:assessment, %{ + is_published: true, + course: course, + config: assessment_config + }) avenger = insert(:course_registration, %{role: :staff, course: course}) student = insert(:course_registration, %{role: :student, course: course}) submission = insert(:submission, %{student: student, assessment: assessment}) @@ -72,7 +76,7 @@ defmodule CadetWeb.AssessmentsControllerTest do conn = get(conn, "/v2/notifications/config/user/#{student.id}") result = Jason.decode!(response(conn, 200)) - assert length(result) == 0 + assert Enum.empty?(result) end test "400 fails, user does not exist", %{conn: conn} do @@ -125,7 +129,7 @@ defmodule CadetWeb.AssessmentsControllerTest do conn = get(conn, "/v2/notifications/options/config/-1") result = Jason.decode!(response(conn, 200)) - assert length(result) == 0 + assert Enum.empty?(result) end end From 594936692a0533968181d5acceaf8d6b15f8a3e3 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 17 Jun 2023 02:55:01 +0800 Subject: [PATCH 67/80] Fix formatting issues --- lib/cadet/notifications.ex | 5 +- .../new_notifications_controller.ex | 1 - .../new_notifications_controller_test.exs | 114 ++++++++++++------ 3 files changed, 80 insertions(+), 40 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index 677602a13..a3afc7aac 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -98,8 +98,11 @@ defmodule Cadet.Notifications do """ def get_configurable_notification_configs(cr_id) do cr = Repo.get(Cadet.Accounts.CourseRegistration, cr_id) + case cr do - nil -> nil + nil -> + nil + _ -> is_staff = cr.role == :staff diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index 7846200f2..8642e0cf4 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -18,7 +18,6 @@ defmodule CadetWeb.NewNotificationsController do nil -> conn |> put_status(400) |> text("course_reg_id does not exist") _ -> render(conn, "configs_full.json", configs: configs) end - end def update_noti_configs(conn, params) do diff --git a/test/cadet_web/controllers/new_notifications_controller_test.exs b/test/cadet_web/controllers/new_notifications_controller_test.exs index bef23665f..a6feff246 100644 --- a/test/cadet_web/controllers/new_notifications_controller_test.exs +++ b/test/cadet_web/controllers/new_notifications_controller_test.exs @@ -10,32 +10,40 @@ defmodule CadetWeb.AssessmentsControllerTest do setup do course = insert(:course) assessment_config = insert(:assessment_config, %{course: course}) - assessment = insert(:assessment, %{ - is_published: true, - course: course, - config: assessment_config - }) + + assessment = + insert(:assessment, %{ + is_published: true, + course: course, + config: assessment_config + }) + avenger = insert(:course_registration, %{role: :staff, course: course}) student = insert(:course_registration, %{role: :student, course: course}) submission = insert(:submission, %{student: student, assessment: assessment}) Ecto.Adapters.SQL.Sandbox.checkout(Cadet.Repo) - course_noticonfig_query = from( - nc in NotificationConfig, - join: ntype in NotificationType, - on: nc.notification_type_id == ntype.id, - where: nc.course_id == ^course.id and is_nil(nc.assessment_config_id) and ntype.for_staff == true, - limit: 1 - ) + course_noticonfig_query = + from( + nc in NotificationConfig, + join: ntype in NotificationType, + on: nc.notification_type_id == ntype.id, + where: + nc.course_id == ^course.id and is_nil(nc.assessment_config_id) and + ntype.for_staff == true, + limit: 1 + ) + course_noticonfig = Cadet.Repo.one(course_noticonfig_query) # insert a notification preference for the avenger - avenger_preference = insert(:notification_preference, %{ - notification_config: course_noticonfig, - course_reg: avenger, - is_enabled: false - }) + avenger_preference = + insert(:notification_preference, %{ + notification_config: course_noticonfig, + course_reg: avenger, + is_enabled: false + }) # insert 2 time options for the notification config time_options = insert_list(2, :time_option, %{notification_config: course_noticonfig}) @@ -56,7 +64,6 @@ defmodule CadetWeb.AssessmentsControllerTest do describe "GET /v2/notifications/config/:course_id" do test "200 suceeds", %{course: course, conn: conn} do - conn = get(conn, "/v2/notifications/config/#{course.id}") result = Jason.decode!(response(conn, 200)) @@ -87,9 +94,11 @@ defmodule CadetWeb.AssessmentsControllerTest do describe "PUT /v2/notifications/config" do test "200 succeeds", %{course_noticonfig: course_noticonfig, conn: conn} do - conn = put(conn, "/v2/notifications/config", %{ - "_json" => [%{:id => course_noticonfig.id, :isEnabled => true}] - }) + conn = + put(conn, "/v2/notifications/config", %{ + "_json" => [%{:id => course_noticonfig.id, :isEnabled => true}] + }) + result = Jason.decode!(response(conn, 200)) assert length(result) == 1 @@ -104,9 +113,18 @@ defmodule CadetWeb.AssessmentsControllerTest do course_noticonfig: course_noticonfig, conn: conn } do - conn = put(conn, "/v2/notifications/preferences", %{ - "_json" => [%{:id => avenger_preference.id, :courseRegId => avenger.id, :notificationConfigId => course_noticonfig.id, :isEnabled => true}] - }) + conn = + put(conn, "/v2/notifications/preferences", %{ + "_json" => [ + %{ + :id => avenger_preference.id, + :courseRegId => avenger.id, + :notificationConfigId => course_noticonfig.id, + :isEnabled => true + } + ] + }) + result = Jason.decode!(response(conn, 200)) assert length(result) == 1 @@ -115,11 +133,16 @@ defmodule CadetWeb.AssessmentsControllerTest do end describe "GET /options/config/:noti_config_id" do - test "200 succeeds", %{course_noticonfig: course_noticonfig, time_options: time_options, conn: conn} do + test "200 succeeds", %{ + course_noticonfig: course_noticonfig, + time_options: time_options, + conn: conn + } do conn = get(conn, "/v2/notifications/options/config/#{course_noticonfig.id}") result = Jason.decode!(response(conn, 200)) assert length(result) == length(time_options) + for {retrieved_to, to} <- Enum.zip(result, time_options) do assert retrieved_to["minutes"] == to.minutes end @@ -143,9 +166,18 @@ defmodule CadetWeb.AssessmentsControllerTest do } do time_option = List.first(time_options) new_minutes = :rand.uniform(200) - conn = put(conn, "/v2/notifications/options", %{ - "_json" => [%{:id => time_option.id, :notificationConfigId => course_noticonfig.id, :minutes => new_minutes}] - }) + + conn = + put(conn, "/v2/notifications/options", %{ + "_json" => [ + %{ + :id => time_option.id, + :notificationConfigId => course_noticonfig.id, + :minutes => new_minutes + } + ] + }) + result = Jason.decode!(response(conn, 200)) assert length(result) == 1 @@ -157,9 +189,12 @@ defmodule CadetWeb.AssessmentsControllerTest do conn: conn } do minutes = :rand.uniform(500) - conn = put(conn, "/v2/notifications/options", %{ - "_json" => [%{:notificationConfigId => course_noticonfig.id, :minutes => minutes}] - }) + + conn = + put(conn, "/v2/notifications/options", %{ + "_json" => [%{:notificationConfigId => course_noticonfig.id, :minutes => minutes}] + }) + result = Jason.decode!(response(conn, 200)) assert length(result) == 1 @@ -173,9 +208,11 @@ defmodule CadetWeb.AssessmentsControllerTest do conn: conn } do time_option = List.first(time_options) - conn = delete(conn, "/v2/notifications/options", %{ - "_json" => [time_option.id] - }) + + conn = + delete(conn, "/v2/notifications/options", %{ + "_json" => [time_option.id] + }) assert response(conn, 200) end @@ -184,10 +221,11 @@ defmodule CadetWeb.AssessmentsControllerTest do test "400 fails, no such time option", %{ conn: conn } do - conn = delete(conn, "/v2/notifications/options", %{ - "_json" => [-1] - }) - assert response(conn, 400) + conn = + delete(conn, "/v2/notifications/options", %{ + "_json" => [-1] + }) + assert response(conn, 400) end end From 9cc08b4163775cb08d7db47903de906d436abe53 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 17 Jun 2023 02:57:34 +0800 Subject: [PATCH 68/80] Fix module name definition --- .../cadet_web/controllers/new_notifications_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cadet_web/controllers/new_notifications_controller_test.exs b/test/cadet_web/controllers/new_notifications_controller_test.exs index a6feff246..a5a217e76 100644 --- a/test/cadet_web/controllers/new_notifications_controller_test.exs +++ b/test/cadet_web/controllers/new_notifications_controller_test.exs @@ -1,6 +1,6 @@ # Results of tests depends on the number of notifications implemented in Source Academy, # test expected values have to be updated as more notification types are introduced -defmodule CadetWeb.AssessmentsControllerTest do +defmodule CadetWeb.NewNotificationsControllerTest do use CadetWeb.ConnCase import Ecto.Query, warn: false From 6c242a027c8f85f4ff463854e674d5be17dda6f1 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 17 Jun 2023 03:55:31 +0800 Subject: [PATCH 69/80] Add more tests and remove unused controller actions --- .../new_notifications_controller.ex | 105 +++--------------- .../notifications/notifications_test.exs | 61 +++++++++- .../notification_type_factory.ex | 2 +- 3 files changed, 75 insertions(+), 93 deletions(-) diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index 8642e0cf4..dfcd63942 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -41,45 +41,28 @@ defmodule CadetWeb.NewNotificationsController do # NOTIFICATION PREFERENCES - def create_preference(conn, params) do - changeset = NotificationPreference.changeset(%NotificationPreference{}, params) + def upsert_noti_preferences(conn, params) do + changesets = + params["_json"] + |> snake_casify_string_keys_recursive() + |> Stream.map(fn noti_pref -> + if noti_pref["id"] < 0 do + Map.delete(noti_pref, "id") + end - case Repo.insert(changeset) do + NotificationPreference.changeset(%NotificationPreference{}, noti_pref) + end) + |> Enum.to_list() + + case Notifications.upsert_many_noti_preferences(changesets) do {:ok, res} -> - pref = Notifications.get_notification_preference!(res.id) - render(conn, "noti_pref.json", noti_pref: pref) + render(conn, "noti_prefs.json", noti_prefs: res) {:error, changeset} -> conn |> put_status(400) |> text(changeset_error_to_string(changeset)) end end - def update_preference(conn, %{ - "noti_pref_id" => id, - "is_enabled" => is_enabled, - "time_option_id" => time_option_id - }) do - pref = Repo.get(NotificationPreference, id) - - if is_nil(pref) do - conn |> put_status(404) |> text("Notification preference of given ID not found") - end - - changeset = - pref - |> NotificationPreference.changeset(%{is_enabled: is_enabled}) - |> NotificationPreference.changeset(%{time_option_id: time_option_id}) - - case Repo.update(changeset) do - {:ok, res} -> - pref = Notifications.get_notification_preference!(res.id) - render(conn, "noti_pref.json", noti_pref: pref) - - {:error, {status, message}} -> - conn |> put_status(status) |> text(message) - end - end - # TIME OPTIONS def get_config_time_options(conn, %{"noti_config_id" => noti_config_id}) do @@ -110,66 +93,6 @@ defmodule CadetWeb.NewNotificationsController do end end - def upsert_noti_preferences(conn, params) do - changesets = - params["_json"] - |> snake_casify_string_keys_recursive() - |> Stream.map(fn noti_pref -> - if noti_pref["id"] < 0 do - Map.delete(noti_pref, "id") - end - - NotificationPreference.changeset(%NotificationPreference{}, noti_pref) - end) - |> Enum.to_list() - - case Notifications.upsert_many_noti_preferences(changesets) do - {:ok, res} -> - render(conn, "noti_prefs.json", noti_prefs: res) - - {:error, changeset} -> - conn |> put_status(400) |> text(changeset_error_to_string(changeset)) - end - end - - # Only allow updating of `is_default` - # should delete and create a new entry for other fields - def update_time_option(conn, %{"time_option_id" => time_option_id, "is_default" => is_default}) do - time_option = Repo.get(TimeOption, time_option_id) - - if is_nil(time_option) do - conn |> put_status(404) |> text("Time option of given ID not found") - end - - changeset = - time_option - |> TimeOption.changeset(%{is_default: is_default}) - - case Repo.update(changeset) do - {:ok, res} -> - render(conn, "time_option.json", time_option: res) - - {:error, {status, message}} -> - conn |> put_status(status) |> text(message) - end - end - - def delete_time_option(conn, %{"time_option_id" => time_option_id}) do - time_option = Repo.get(TimeOption, time_option_id) - - if is_nil(time_option) do - conn |> put_status(404) |> text("Time option of given ID not found") - end - - case Repo.delete(time_option) do - {:ok, res} -> - render(conn, "time_option.json", time_option: res) - - {:error, {status, message}} -> - conn |> put_status(status) |> text(message) - end - end - def delete_time_options(conn, params) do # time_option = Repo.get(TimeOption, time_option_id) diff --git a/test/cadet/notifications/notifications_test.exs b/test/cadet/notifications/notifications_test.exs index 5640eeebd..cd91e5816 100644 --- a/test/cadet/notifications/notifications_test.exs +++ b/test/cadet/notifications/notifications_test.exs @@ -15,6 +15,13 @@ defmodule Cadet.NotificationsTest do describe "notification_configs" do @invalid_attrs %{is_enabled: nil} + test "get_notification_config!/1 returns the notification_config with given id" do + notification_config = insert(:notification_config) + + assert Notifications.get_notification_config!(notification_config.id).id == + notification_config.id + end + test "get_notification_config!/3 returns the notification_config with given id" do notification_config = insert(:notification_config) @@ -35,6 +42,16 @@ defmodule Cadet.NotificationsTest do ).id == notification_config.id end + test "get_notification_configs!/1 returns all inserted notification configs" do + course = insert(:course) + assessment_config = insert(:assessment_config, course: course) + notification_config_1 = insert(:notification_config, assessment_config: assessment_config) + notification_config_2 = insert(:notification_config, assessment_config: nil) + + result = Notifications.get_notification_configs(course.id) + assert length(result) == 2 + end + test "update_notification_config/2 with valid data updates the notification_config" do notification_config = insert(:notification_config) update_attrs = %{is_enabled: true} @@ -93,6 +110,19 @@ defmodule Cadet.NotificationsTest do ).id == time_option.id end + test "get_time_options_for_config/1 returns the time_options that belongs to the notification config" do + notification_config = insert(:notification_config) + + time_options = + insert_list(3, :time_option, %{ + :notification_config => notification_config, + :is_default => true + }) + + assert length(Notifications.get_time_options_for_config(notification_config.id)) == + length(time_options) + end + test "create_time_option/1 with invalid data returns error changeset" do assert {:error, %Ecto.Changeset{}} = Notifications.create_time_option(@invalid_attrs) end @@ -102,13 +132,42 @@ defmodule Cadet.NotificationsTest do assert {:ok, %TimeOption{}} = Notifications.delete_time_option(time_option) assert_raise Ecto.NoResultsError, fn -> Notifications.get_time_option!(time_option.id) end end + + test "delete_many_time_options/1 deletes all the time_options" do + time_options = insert_list(3, :time_option) + ids = Enum.map(time_options, fn to -> to.id end) + + Notifications.delete_many_time_options(ids) + + for to <- time_options do + assert_raise Ecto.NoResultsError, fn -> Notifications.get_time_option!(to.id) end + end + end + + test "delete_many_time_options/1 rollsback on failed deletion" do + time_options = insert_list(3, :time_option) + ids = Enum.map(time_options, fn to -> to.id end) ++ [-1] + + assert {:error, _} = Notifications.delete_many_time_options(ids) + + for to <- time_options do + assert %TimeOption{} = Notifications.get_time_option!(to.id) + end + end end describe "notification_preferences" do @invalid_attrs %{is_enabled: nil} test "get_notification_preference!/1 returns the notification_preference with given id" do - notification_type = insert(:notification_type, name: "get_notification_preference!/1") + notification_preference = insert(:notification_preference) + + assert Notifications.get_notification_preference!(notification_preference.id).id == + notification_preference.id + end + + test "get_notification_preference/2 returns the notification_preference with given values" do + notification_type = insert(:notification_type, name: "get_notification_preference/2") notification_config = insert(:notification_config, notification_type: notification_type) notification_preference = diff --git a/test/factories/notifications/notification_type_factory.ex b/test/factories/notifications/notification_type_factory.ex index 5c7995564..447746879 100644 --- a/test/factories/notifications/notification_type_factory.ex +++ b/test/factories/notifications/notification_type_factory.ex @@ -11,7 +11,7 @@ defmodule Cadet.Notifications.NotificationTypeFactory do %NotificationType{ is_autopopulated: false, is_enabled: false, - name: "Generic Notificaation Type", + name: Faker.Pokemon.name(), template_file_name: "generic_template_name" } end From df05af4b99919c5c29aefebc18da01dcd2dd14c8 Mon Sep 17 00:00:00 2001 From: Santosh Muthukrishnan Date: Sun, 18 Jun 2023 16:30:25 -0700 Subject: [PATCH 70/80] Add user level configuration for avenger backlog --- lib/cadet/workers/NotificationWorker.ex | 42 ++++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index d96a3df22..779e97cff 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -73,31 +73,35 @@ defmodule Cadet.Workers.NotificationWorker do for avenger_cr <- avengers_crs do avenger = Cadet.Accounts.get_user(avenger_cr.user_id) - ungraded_submissions = - Jason.decode!( - elem( - Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), - 1 + if is_user_enabled(notification_type_id, avenger_cr.id) do + ungraded_submissions = + Jason.decode!( + elem( + Cadet.Assessments.all_submissions_by_grader_for_index(avenger_cr, true, true), + 1 + ) ) - ) - if length(ungraded_submissions) < ungraded_threshold do - IO.puts("[AVENGER_BACKLOG] below threshold!") - else - IO.puts("[AVENGER_BACKLOG] SENDING_OUT") + if length(ungraded_submissions) < ungraded_threshold do + IO.puts("[AVENGER_BACKLOG] below threshold!") + else + IO.puts("[AVENGER_BACKLOG] SENDING_OUT") - email = - Email.avenger_backlog_email( - ntype.template_file_name, - avenger, - ungraded_submissions - ) + email = + Email.avenger_backlog_email( + ntype.template_file_name, + avenger, + ungraded_submissions + ) - {status, email} = Mailer.deliver_now(email) + {status, email} = Mailer.deliver_now(email) - if status == :ok do - Notifications.create_sent_notification(avenger_cr.id, email.html_body) + if status == :ok do + Notifications.create_sent_notification(avenger_cr.id, email.html_body) + end end + else + IO.puts("[ASSESSMENT_SUBMISSION] user-level disabled") end end else From eeae507300ccdb656a6679db2b7fb4499b2aeebe Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Sun, 2 Jul 2023 20:53:33 -0700 Subject: [PATCH 71/80] update gettext --- mix.exs | 1 + mix.lock | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 0e7a00f3a..a7d2b9d9c 100644 --- a/mix.exs +++ b/mix.exs @@ -80,6 +80,7 @@ defmodule Cadet.Mixfile do {:sentry, "~> 8.0"}, {:sweet_xml, "~> 0.6"}, {:timex, "~> 3.7"}, + {:gettext, "~> 0.22.3"}, # notifiations system dependencies {:phoenix_html, "~> 3.0"}, diff --git a/mix.lock b/mix.lock index 45a64308d..0bcb98064 100644 --- a/mix.lock +++ b/mix.lock @@ -38,11 +38,12 @@ "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, + "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "exvcr": {:hex, :exvcr, "0.13.3", "fcd5f54ea0ebd41db7fe16701f3c67871d1b51c3c104ab88f11135a173d47134", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "db61057447388b7adc4443a55047d11d09acc75eeb5548507c775a8402e02689"}, "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "gen_stage": {:hex, :gen_stage, "1.1.2", "b1656cd4ba431ed02c5656fe10cb5423820847113a07218da68eae5d6a260c23", [:mix], [], "hexpm", "9e39af23140f704e2b07a3e29d8f05fd21c2aaf4088ff43cb82be4b9e3148d02"}, - "gettext": {:hex, :gettext, "0.19.1", "564953fd21f29358e68b91634799d9d26989f8d039d7512622efb3c3b1c97892", [:mix], [], "hexpm", "10c656c0912b8299adba9b061c06947511e3f109ab0d18b44a866a4498e77222"}, + "gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"}, "git_hooks": {:hex, :git_hooks, "0.7.3", "09489e94d88dfc767662e22aff2b6208bd7cf555a19dd0e1477cca4683ce0701", [:mix], [{:blankable, "~> 1.0.0", [hex: :blankable, repo: "hexpm", optional: false]}, {:recase, "~> 0.7.0", [hex: :recase, repo: "hexpm", optional: false]}], "hexpm", "d6ddedeb4d3a8602bc3f84e087a38f6150a86d9e790628ed8bc70e6d90681659"}, "guardian": {:hex, :guardian, "2.2.4", "3dafdc19665411c96b2796d184064d691bc08813a132da5119e39302a252b755", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "6f83d4309c16ec2469da8606bb2a9815512cc2fac1595ad34b79940a224eb110"}, "guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"}, From 05291a721c00d28c939dc14591183f155edb15e5 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Mon, 3 Jul 2023 04:57:13 -0700 Subject: [PATCH 72/80] modify get router --- lib/cadet/notifications.ex | 42 +++++++++++++++++++++----------------- lib/cadet_web/router.ex | 12 +++++++++-- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index a3afc7aac..eb4228e4f 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -4,6 +4,7 @@ defmodule Cadet.Notifications do """ import Ecto.Query, warn: false + require Logger alias Cadet.Repo alias Cadet.Notifications.{ @@ -49,32 +50,35 @@ defmodule Cadet.Notifications do def get_notification_config!(notification_type_id, course_id, assconfig_id) do query = - from(n in Cadet.Notifications.NotificationConfig, - join: ntype in Cadet.Notifications.NotificationType, - on: n.notification_type_id == ntype.id, - where: n.notification_type_id == ^notification_type_id and n.course_id == ^course_id + Cadet.Notifications.NotificationConfig + |> join(:inner, [n], ntype in Cadet.Notifications.NotificationType, + on: n.notification_type_id == ntype.id ) + |> where([n], n.notification_type_id == ^notification_type_id and n.course_id == ^course_id) + |> filter_assconfig_id(assconfig_id) + |> Repo.one() - query = - if is_nil(assconfig_id) do - where(query, [c], is_nil(c.assessment_config_id)) - else - where(query, [c], c.assessment_config_id == ^assconfig_id) - end - - config = Repo.one(query) + case query do + nil -> + Logger.error( + "No NotificationConfig found for Course #{course_id} and NotificationType #{notification_type_id}" + ) - if config != nil do - config - else - IO.puts( - "No NotificationConfig found for Course #{course_id} and NotificationType #{notification_type_id}" - ) + nil - nil + config -> + config end end + defp filter_assconfig_id(query, nil) do + query |> where([c], is_nil(c.assessment_config_id)) + end + + defp filter_assconfig_id(query, assconfig_id) do + query |> where([c], c.assessment_config_id == ^assconfig_id) + end + def get_notification_config!(id), do: Repo.get!(NotificationConfig, id) @doc """ diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index 437f75d3b..a2a590832 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -70,6 +70,12 @@ defmodule CadetWeb.Router do scope "/v2/courses/:course_id", CadetWeb do pipe_through([:api, :auth, :ensure_auth, :course]) + get( + "/notifications/config/user/:course_reg_id", + NewNotificationsController, + :get_configurable_noti_configs + ) + get("/sourcecast", SourcecastController, :index) get("/assessments", AssessmentsController, :index) @@ -106,6 +112,8 @@ defmodule CadetWeb.Router do resources("/sourcecast", AdminSourcecastController, only: [:create, :delete]) + get("/notifications/config", NewNotificationsController, :all_noti_configs) + get("/assets/:foldername", AdminAssetsController, :index) post("/assets/:foldername/*filename", AdminAssetsController, :upload) delete("/assets/:foldername/*filename", AdminAssetsController, :delete) @@ -171,8 +179,8 @@ defmodule CadetWeb.Router do # Notifications endpoints scope "/v2/notifications/", CadetWeb do - get("/config/:course_id", NewNotificationsController, :all_noti_configs) - get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) + # get("/config/:course_id", NewNotificationsController, :all_noti_configs) + # get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) put("/config/", NewNotificationsController, :update_noti_configs) put("/preferences", NewNotificationsController, :upsert_noti_preferences) From 45fae7a4c2ff25c8f9027cbf4a94a58717c1ee04 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Thu, 6 Jul 2023 03:00:28 -0700 Subject: [PATCH 73/80] 5routerdone --- lib/cadet_web/router.ex | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index a2a590832..fd0c09678 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -76,6 +76,8 @@ defmodule CadetWeb.Router do :get_configurable_noti_configs ) + put("/notifications/options", NewNotificationsController, :upsert_time_options) + get("/sourcecast", SourcecastController, :index) get("/assessments", AssessmentsController, :index) @@ -113,6 +115,8 @@ defmodule CadetWeb.Router do resources("/sourcecast", AdminSourcecastController, only: [:create, :delete]) get("/notifications/config", NewNotificationsController, :all_noti_configs) + put("/notifications/config", NewNotificationsController, :update_noti_configs) + delete("/notifications/options", NewNotificationsController, :delete_time_options) get("/assets/:foldername", AdminAssetsController, :index) post("/assets/:foldername/*filename", AdminAssetsController, :upload) @@ -181,13 +185,14 @@ defmodule CadetWeb.Router do scope "/v2/notifications/", CadetWeb do # get("/config/:course_id", NewNotificationsController, :all_noti_configs) # get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) - put("/config/", NewNotificationsController, :update_noti_configs) + # put("/config/", NewNotificationsController, :update_noti_configs) put("/preferences", NewNotificationsController, :upsert_noti_preferences) get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) - put("/options", NewNotificationsController, :upsert_time_options) - delete("/options", NewNotificationsController, :delete_time_options) + # put("/options", NewNotificationsController, :upsert_time_options) + + # delete("/options", NewNotificationsController, :delete_time_options) end # Other scopes may use custom stacks. From f2d9d3663f02ff827b1c11a2978d4bfb4a113f09 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Thu, 6 Jul 2023 22:47:15 -0700 Subject: [PATCH 74/80] allrouters --- lib/cadet_web/router.ex | 14 ++++++++++++-- mix.exs | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index fd0c09678..d7950817f 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -70,6 +70,7 @@ defmodule CadetWeb.Router do scope "/v2/courses/:course_id", CadetWeb do pipe_through([:api, :auth, :ensure_auth, :course]) + # notification routers get( "/notifications/config/user/:course_reg_id", NewNotificationsController, @@ -77,6 +78,13 @@ defmodule CadetWeb.Router do ) put("/notifications/options", NewNotificationsController, :upsert_time_options) + put("/notifications/preferences", NewNotificationsController, :upsert_noti_preferences) + # un-used? + get( + "notifications/options/config/:noti_config_id", + NewNotificationsController, + :get_config_time_options + ) get("/sourcecast", SourcecastController, :index) @@ -114,6 +122,7 @@ defmodule CadetWeb.Router do resources("/sourcecast", AdminSourcecastController, only: [:create, :delete]) + # notification routers get("/notifications/config", NewNotificationsController, :all_noti_configs) put("/notifications/config", NewNotificationsController, :update_noti_configs) delete("/notifications/options", NewNotificationsController, :delete_time_options) @@ -187,9 +196,10 @@ defmodule CadetWeb.Router do # get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) # put("/config/", NewNotificationsController, :update_noti_configs) - put("/preferences", NewNotificationsController, :upsert_noti_preferences) + ## put("/preferences", NewNotificationsController, :upsert_noti_preferences) + + ## get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) - get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) # put("/options", NewNotificationsController, :upsert_time_options) # delete("/options", NewNotificationsController, :delete_time_options) diff --git a/mix.exs b/mix.exs index a7d2b9d9c..52b442c55 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,8 @@ defmodule Cadet.Mixfile do version: "0.0.1", elixir: "~> 1.10", elixirc_paths: elixirc_paths(Mix.env()), - compilers: [:phoenix, :gettext] ++ Mix.compilers() ++ [:phoenix_swagger], + # compilers: [:phoenix, :gettext] ++ Mix.compilers() ++ [:phoenix_swagger], + compilers: [:phoenix] ++ Mix.compilers() ++ [:phoenix_swagger], start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], preferred_cli_env: [ From d3ff8ddc6fd249c8bfbf1482afa44ac7b5ec3547 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Mon, 10 Jul 2023 02:20:54 -0700 Subject: [PATCH 75/80] modifycodestyle --- lib/cadet/notifications.ex | 78 +++++++++++++------------ lib/cadet/workers/NotificationWorker.ex | 19 +++--- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/lib/cadet/notifications.ex b/lib/cadet/notifications.ex index eb4228e4f..da9ad09bc 100644 --- a/lib/cadet/notifications.ex +++ b/lib/cadet/notifications.ex @@ -86,12 +86,11 @@ defmodule Cadet.Notifications do """ def get_notification_configs(course_id) do query = - from(n in Cadet.Notifications.NotificationConfig, - where: n.course_id == ^course_id - ) + Cadet.Notifications.NotificationConfig + |> where([n], n.course_id == ^course_id) + |> Repo.all() query - |> Repo.all() |> Repo.preload([:notification_type, :course, :assessment_config, :time_options]) end @@ -111,23 +110,26 @@ defmodule Cadet.Notifications do is_staff = cr.role == :staff query = - from(n in Cadet.Notifications.NotificationConfig, - join: ntype in Cadet.Notifications.NotificationType, - on: n.notification_type_id == ntype.id, - join: c in Cadet.Courses.Course, - on: n.course_id == c.id, - left_join: ac in Cadet.Courses.AssessmentConfig, - on: n.assessment_config_id == ac.id, - left_join: p in Cadet.Notifications.NotificationPreference, - on: p.notification_config_id == n.id, - where: - ntype.for_staff == ^is_staff and - n.course_id == ^cr.course_id and - (p.course_reg_id == ^cr.id or is_nil(p.course_reg_id)) + Cadet.Notifications.NotificationConfig + |> join(:inner, [n], ntype in Cadet.Notifications.NotificationType, + on: n.notification_type_id == ntype.id + ) + |> join(:inner, [n], c in Cadet.Courses.Course, on: n.course_id == c.id) + |> join(:left, [n], ac in Cadet.Courses.AssessmentConfig, + on: n.assessment_config_id == ac.id + ) + |> join(:left, [n], p in Cadet.Notifications.NotificationPreference, + on: p.notification_config_id == n.id ) + |> where( + [n, ntype, c, ac, p], + ntype.for_staff == ^is_staff and + n.course_id == ^cr.course_id and + (p.course_reg_id == ^cr.id or is_nil(p.course_reg_id)) + ) + |> Repo.all() query - |> Repo.all() |> Repo.preload([ :notification_type, :course, @@ -201,13 +203,14 @@ defmodule Cadet.Notifications do """ def get_time_options_for_config(notification_config_id) do query = - from(to in Cadet.Notifications.TimeOption, - join: nc in Cadet.Notifications.NotificationConfig, - on: to.notification_config_id == nc.id, - where: nc.id == ^notification_config_id + Cadet.Notifications.TimeOption + |> join(:inner, [to], nc in Cadet.Notifications.NotificationConfig, + on: to.notification_config_id == nc.id ) + |> where([to, nc], nc.id == ^notification_config_id) + |> Repo.all() - Repo.all(query) + query end @doc """ @@ -313,13 +316,15 @@ defmodule Cadet.Notifications do for to_id <- to_ids do time_option = Repo.get(TimeOption, to_id) - if is_nil(time_option) do - Repo.rollback("Time option do not exist") - else - case Repo.delete(time_option) do - {:ok, time_option} -> time_option - {:delete_error, error} -> Repo.rollback(error) - end + case time_option do + nil -> + Repo.rollback("Time option does not exist") + + _ -> + case Repo.delete(time_option) do + {:ok, deleted_time_option} -> deleted_time_option + {:delete_error, error} -> Repo.rollback(error) + end end end end) @@ -330,14 +335,13 @@ defmodule Cadet.Notifications do """ def get_notification_preference!(notification_preference_id) do query = - from(np in NotificationPreference, - left_join: to in TimeOption, - on: to.id == np.time_option_id, - where: np.id == ^notification_preference_id, - preload: :time_option - ) + NotificationPreference + |> join(:left, [np], to in TimeOption, on: to.id == np.time_option_id) + |> where([np, to], np.id == ^notification_preference_id) + |> preload(:time_option) + |> Repo.one!() - Repo.one!(query) + query end @doc """ diff --git a/lib/cadet/workers/NotificationWorker.ex b/lib/cadet/workers/NotificationWorker.ex index 779e97cff..598689cee 100644 --- a/lib/cadet/workers/NotificationWorker.ex +++ b/lib/cadet/workers/NotificationWorker.ex @@ -3,6 +3,7 @@ defmodule Cadet.Workers.NotificationWorker do Contain oban workers for sending notifications """ use Oban.Worker, queue: :notifications, max_attempts: 1 + require Logger alias Cadet.{Email, Notifications, Mailer} alias Cadet.Repo @@ -83,9 +84,9 @@ defmodule Cadet.Workers.NotificationWorker do ) if length(ungraded_submissions) < ungraded_threshold do - IO.puts("[AVENGER_BACKLOG] below threshold!") + Logger.info("[AVENGER_BACKLOG] below threshold!") else - IO.puts("[AVENGER_BACKLOG] SENDING_OUT") + Logger.info("[AVENGER_BACKLOG] SENDING_OUT") email = Email.avenger_backlog_email( @@ -101,15 +102,15 @@ defmodule Cadet.Workers.NotificationWorker do end end else - IO.puts("[ASSESSMENT_SUBMISSION] user-level disabled") + Logger.info("[ASSESSMENT_SUBMISSION] user-level disabled") end end else - IO.puts("[AVENGER_BACKLOG] course-level disabled") + Logger.info("[AVENGER_BACKLOG] course-level disabled") end end else - IO.puts("[AVENGER_BACKLOG] system-level disabled!") + Logger.info("[AVENGER_BACKLOG] system-level disabled!") end :ok @@ -136,13 +137,13 @@ defmodule Cadet.Workers.NotificationWorker do cond do !is_course_enabled(notification_type.id, course_id, assessment_config_id) -> - IO.puts("[ASSESSMENT_SUBMISSION] course-level disabled") + Logger.info("[ASSESSMENT_SUBMISSION] course-level disabled") !is_user_enabled(notification_type.id, avenger_cr.id) -> - IO.puts("[ASSESSMENT_SUBMISSION] user-level disabled") + Logger.info("[ASSESSMENT_SUBMISSION] user-level disabled") true -> - IO.puts("[ASSESSMENT_SUBMISSION] SENDING_OUT") + Logger.info("[ASSESSMENT_SUBMISSION] SENDING_OUT") email = Email.assessment_submission_email( @@ -159,7 +160,7 @@ defmodule Cadet.Workers.NotificationWorker do end end else - IO.puts("[ASSESSMENT_SUBMISSION] system-level disabled!") + Logger.info("[ASSESSMENT_SUBMISSION] system-level disabled!") end end end From 0cd5f3ba861488329a740de90014417a11c6ecca Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Sun, 16 Jul 2023 19:22:43 -0700 Subject: [PATCH 76/80] modify admin page --- ...2_change_avenger_backlog_notification_type.exs | 15 +++++++++++++++ .../new_notifications_controller_test.exs | 4 ++++ 2 files changed, 19 insertions(+) create mode 100644 priv/repo/migrations/20230714090532_change_avenger_backlog_notification_type.exs diff --git a/priv/repo/migrations/20230714090532_change_avenger_backlog_notification_type.exs b/priv/repo/migrations/20230714090532_change_avenger_backlog_notification_type.exs new file mode 100644 index 000000000..9dc4a52ca --- /dev/null +++ b/priv/repo/migrations/20230714090532_change_avenger_backlog_notification_type.exs @@ -0,0 +1,15 @@ +defmodule Cadet.Repo.Migrations.ChangeAvengerBacklogNotificationType do + use Ecto.Migration + + def up do + execute( + "UPDATE notification_types SET is_autopopulated = FALSE WHERE name = 'AVENGER BACKLOG'" + ) + end + + def down do + execute( + "UPDATE notification_types SET is_autopopulated = TRUE WHERE name = 'AVENGER BACKLOG'" + ) + end +end diff --git a/test/cadet_web/controllers/new_notifications_controller_test.exs b/test/cadet_web/controllers/new_notifications_controller_test.exs index a5a217e76..d5abf38a4 100644 --- a/test/cadet_web/controllers/new_notifications_controller_test.exs +++ b/test/cadet_web/controllers/new_notifications_controller_test.exs @@ -62,6 +62,8 @@ defmodule CadetWeb.NewNotificationsControllerTest do }} end + # /v2/courses/#{course.id}/admin/notifications/config + # conn = get(conn, "/v2/notifications/config/#{course.id}") describe "GET /v2/notifications/config/:course_id" do test "200 suceeds", %{course: course, conn: conn} do conn = get(conn, "/v2/notifications/config/#{course.id}") @@ -71,6 +73,8 @@ defmodule CadetWeb.NewNotificationsControllerTest do end end + # /v2/courses/#{course.id}/notifications/config/user/#{avenger.id} + # conn = get(conn, "/v2/notifications/config/user/#{avenger.id}") describe "GET /v2/notifications/config/user/:course_reg_id" do test "200 succeeds for avenger", %{avenger: avenger, conn: conn} do conn = get(conn, "/v2/notifications/config/user/#{avenger.id}") From eeb0bbe8812958a2816a7004bafcafa07b302a62 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Tue, 18 Jul 2023 04:25:06 -0700 Subject: [PATCH 77/80] delete useless comments --- lib/cadet_web/router.ex | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index d7950817f..dcfb9ad79 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -79,7 +79,6 @@ defmodule CadetWeb.Router do put("/notifications/options", NewNotificationsController, :upsert_time_options) put("/notifications/preferences", NewNotificationsController, :upsert_noti_preferences) - # un-used? get( "notifications/options/config/:noti_config_id", NewNotificationsController, @@ -190,20 +189,6 @@ defmodule CadetWeb.Router do ) end - # Notifications endpoints - scope "/v2/notifications/", CadetWeb do - # get("/config/:course_id", NewNotificationsController, :all_noti_configs) - # get("/config/user/:course_reg_id", NewNotificationsController, :get_configurable_noti_configs) - # put("/config/", NewNotificationsController, :update_noti_configs) - - ## put("/preferences", NewNotificationsController, :upsert_noti_preferences) - - ## get("/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options) - - # put("/options", NewNotificationsController, :upsert_time_options) - - # delete("/options", NewNotificationsController, :delete_time_options) - end # Other scopes may use custom stacks. # scope "/api", CadetWeb do From 88fa350a888f6637776ccf5932a714e4833d3726 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Tue, 18 Jul 2023 04:43:31 -0700 Subject: [PATCH 78/80] delete --- .../new_notifications_controller_test.exs | 235 ------------------ 1 file changed, 235 deletions(-) delete mode 100644 test/cadet_web/controllers/new_notifications_controller_test.exs diff --git a/test/cadet_web/controllers/new_notifications_controller_test.exs b/test/cadet_web/controllers/new_notifications_controller_test.exs deleted file mode 100644 index d5abf38a4..000000000 --- a/test/cadet_web/controllers/new_notifications_controller_test.exs +++ /dev/null @@ -1,235 +0,0 @@ -# Results of tests depends on the number of notifications implemented in Source Academy, -# test expected values have to be updated as more notification types are introduced -defmodule CadetWeb.NewNotificationsControllerTest do - use CadetWeb.ConnCase - - import Ecto.Query, warn: false - - alias Cadet.Notifications.{NotificationConfig, NotificationType} - - setup do - course = insert(:course) - assessment_config = insert(:assessment_config, %{course: course}) - - assessment = - insert(:assessment, %{ - is_published: true, - course: course, - config: assessment_config - }) - - avenger = insert(:course_registration, %{role: :staff, course: course}) - student = insert(:course_registration, %{role: :student, course: course}) - submission = insert(:submission, %{student: student, assessment: assessment}) - - Ecto.Adapters.SQL.Sandbox.checkout(Cadet.Repo) - - course_noticonfig_query = - from( - nc in NotificationConfig, - join: ntype in NotificationType, - on: nc.notification_type_id == ntype.id, - where: - nc.course_id == ^course.id and is_nil(nc.assessment_config_id) and - ntype.for_staff == true, - limit: 1 - ) - - course_noticonfig = Cadet.Repo.one(course_noticonfig_query) - - # insert a notification preference for the avenger - avenger_preference = - insert(:notification_preference, %{ - notification_config: course_noticonfig, - course_reg: avenger, - is_enabled: false - }) - - # insert 2 time options for the notification config - time_options = insert_list(2, :time_option, %{notification_config: course_noticonfig}) - - {:ok, - %{ - course: course, - assessment_config: assessment_config, - assessment: assessment, - avenger: avenger, - student: student, - submission: submission, - course_noticonfig: course_noticonfig, - avenger_preference: avenger_preference, - time_options: time_options - }} - end - - # /v2/courses/#{course.id}/admin/notifications/config - # conn = get(conn, "/v2/notifications/config/#{course.id}") - describe "GET /v2/notifications/config/:course_id" do - test "200 suceeds", %{course: course, conn: conn} do - conn = get(conn, "/v2/notifications/config/#{course.id}") - result = Jason.decode!(response(conn, 200)) - - assert length(result) == 2 - end - end - - # /v2/courses/#{course.id}/notifications/config/user/#{avenger.id} - # conn = get(conn, "/v2/notifications/config/user/#{avenger.id}") - describe "GET /v2/notifications/config/user/:course_reg_id" do - test "200 succeeds for avenger", %{avenger: avenger, conn: conn} do - conn = get(conn, "/v2/notifications/config/user/#{avenger.id}") - result = Jason.decode!(response(conn, 200)) - - assert length(result) == 2 - end - - test "200 succeeds for student", %{student: student, conn: conn} do - conn = get(conn, "/v2/notifications/config/user/#{student.id}") - result = Jason.decode!(response(conn, 200)) - - assert Enum.empty?(result) - end - - test "400 fails, user does not exist", %{conn: conn} do - conn = get(conn, "/v2/notifications/config/user/-1") - assert response(conn, 400) - end - end - - describe "PUT /v2/notifications/config" do - test "200 succeeds", %{course_noticonfig: course_noticonfig, conn: conn} do - conn = - put(conn, "/v2/notifications/config", %{ - "_json" => [%{:id => course_noticonfig.id, :isEnabled => true}] - }) - - result = Jason.decode!(response(conn, 200)) - - assert length(result) == 1 - assert List.first(result)["isEnabled"] == true - end - end - - describe "PUT /v2/notifications/preferences" do - test "200 succeeds, update", %{ - avenger_preference: avenger_preference, - avenger: avenger, - course_noticonfig: course_noticonfig, - conn: conn - } do - conn = - put(conn, "/v2/notifications/preferences", %{ - "_json" => [ - %{ - :id => avenger_preference.id, - :courseRegId => avenger.id, - :notificationConfigId => course_noticonfig.id, - :isEnabled => true - } - ] - }) - - result = Jason.decode!(response(conn, 200)) - - assert length(result) == 1 - assert List.first(result)["isEnabled"] == true - end - end - - describe "GET /options/config/:noti_config_id" do - test "200 succeeds", %{ - course_noticonfig: course_noticonfig, - time_options: time_options, - conn: conn - } do - conn = get(conn, "/v2/notifications/options/config/#{course_noticonfig.id}") - result = Jason.decode!(response(conn, 200)) - - assert length(result) == length(time_options) - - for {retrieved_to, to} <- Enum.zip(result, time_options) do - assert retrieved_to["minutes"] == to.minutes - end - end - - test "200 succeeds, empty array as notification config record not found", %{conn: conn} do - conn = get(conn, "/v2/notifications/options/config/-1") - result = Jason.decode!(response(conn, 200)) - - assert Enum.empty?(result) - end - end - - # Due to unique constraint on the column 'minutes', - # test cases may fail if the generator produces the same number - describe "PUT /v2/notifications/options" do - test "200 succeeds, update", %{ - time_options: time_options, - course_noticonfig: course_noticonfig, - conn: conn - } do - time_option = List.first(time_options) - new_minutes = :rand.uniform(200) - - conn = - put(conn, "/v2/notifications/options", %{ - "_json" => [ - %{ - :id => time_option.id, - :notificationConfigId => course_noticonfig.id, - :minutes => new_minutes - } - ] - }) - - result = Jason.decode!(response(conn, 200)) - - assert length(result) == 1 - assert List.first(result)["minutes"] == new_minutes - end - - test "200 succeeds, insert", %{ - course_noticonfig: course_noticonfig, - conn: conn - } do - minutes = :rand.uniform(500) - - conn = - put(conn, "/v2/notifications/options", %{ - "_json" => [%{:notificationConfigId => course_noticonfig.id, :minutes => minutes}] - }) - - result = Jason.decode!(response(conn, 200)) - - assert length(result) == 1 - assert List.first(result)["minutes"] == minutes - end - end - - describe "DELETE /v2/notifications/options" do - test "200 succeeds", %{ - time_options: time_options, - conn: conn - } do - time_option = List.first(time_options) - - conn = - delete(conn, "/v2/notifications/options", %{ - "_json" => [time_option.id] - }) - - assert response(conn, 200) - end - end - - test "400 fails, no such time option", %{ - conn: conn - } do - conn = - delete(conn, "/v2/notifications/options", %{ - "_json" => [-1] - }) - - assert response(conn, 400) - end -end From 283a518ff7320c0d233e307a79ccce8a34bb377d Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Tue, 18 Jul 2023 04:54:24 -0700 Subject: [PATCH 79/80] delete useless comments --- lib/cadet_web/controllers/new_notifications_controller.ex | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/cadet_web/controllers/new_notifications_controller.ex b/lib/cadet_web/controllers/new_notifications_controller.ex index dfcd63942..01ea0a4b5 100644 --- a/lib/cadet_web/controllers/new_notifications_controller.ex +++ b/lib/cadet_web/controllers/new_notifications_controller.ex @@ -94,14 +94,6 @@ defmodule CadetWeb.NewNotificationsController do end def delete_time_options(conn, params) do - # time_option = Repo.get(TimeOption, time_option_id) - - # if is_nil(time_option) do - # conn |> put_status(404) |> text("Time option of given ID not found") - # end - - # case Repo.delete(time_option) do - # JUNYI AND SANTOSH: NOT DEFINED??? case Notifications.delete_many_time_options(params["_json"]) do {:ok, res} -> render(conn, "time_options.json", time_options: res) From 09812775807963e531d1f51dee2247669e952532 Mon Sep 17 00:00:00 2001 From: Catherine9898 Date: Tue, 18 Jul 2023 23:30:39 -0700 Subject: [PATCH 80/80] add slash --- lib/cadet_web/router.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cadet_web/router.ex b/lib/cadet_web/router.ex index dcfb9ad79..c79f84fad 100644 --- a/lib/cadet_web/router.ex +++ b/lib/cadet_web/router.ex @@ -79,8 +79,9 @@ defmodule CadetWeb.Router do put("/notifications/options", NewNotificationsController, :upsert_time_options) put("/notifications/preferences", NewNotificationsController, :upsert_noti_preferences) + get( - "notifications/options/config/:noti_config_id", + "/notifications/options/config/:noti_config_id", NewNotificationsController, :get_config_time_options ) @@ -189,7 +190,6 @@ defmodule CadetWeb.Router do ) end - # Other scopes may use custom stacks. # scope "/api", CadetWeb do # pipe_through :api