From 8e5780c676b0e005eeb31b9aa1e06fcfa19a312e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Rodrigues?= <93675410+MarioRodrigues10@users.noreply.github.com> Date: Fri, 28 Jul 2023 22:40:00 +0100 Subject: [PATCH] Protect Routes (#292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Lobo --- lib/atomic/accounts.ex | 9 ++ lib/atomic/accounts/user.ex | 3 +- lib/atomic/activities.ex | 28 +++++ lib/atomic/activities/speaker.ex | 11 +- lib/atomic/departments.ex | 13 ++ lib/atomic/organizations.ex | 31 ++++- lib/atomic/partnerships.ex | 13 ++ lib/atomic/partnerships/partner.ex | 2 +- lib/atomic_web/controllers/user_auth.ex | 32 +++-- lib/atomic_web/exceptions.ex | 4 + lib/atomic_web/live/activity_live/edit.ex | 22 ++-- .../live/activity_live/edit.html.heex | 2 +- .../live/activity_live/form_component.ex | 20 ++- lib/atomic_web/live/activity_live/index.ex | 10 +- .../live/activity_live/index.html.heex | 4 +- .../live/activity_live/new.html.heex | 2 +- lib/atomic_web/live/activity_live/show.ex | 22 ++-- .../live/activity_live/show.html.heex | 8 +- lib/atomic_web/live/board_live/edit.ex | 18 +-- lib/atomic_web/live/board_live/index.ex | 2 +- lib/atomic_web/live/board_live/new.ex | 4 +- lib/atomic_web/live/board_live/show.ex | 16 ++- lib/atomic_web/live/department_live/index.ex | 34 ++--- .../live/department_live/index.html.heex | 10 +- lib/atomic_web/live/department_live/show.ex | 18 +-- .../live/department_live/show.html.heex | 10 +- lib/atomic_web/live/home_live/index.ex | 16 +++ lib/atomic_web/live/home_live/index.html.heex | 3 + lib/atomic_web/live/hooks.ex | 63 +++++++++- lib/atomic_web/live/membership_live/edit.ex | 26 ++-- lib/atomic_web/live/membership_live/index.ex | 2 +- lib/atomic_web/live/membership_live/new.ex | 4 +- lib/atomic_web/live/membership_live/show.ex | 14 ++- .../live/organization_live/index.ex | 18 ++- .../live/organization_live/index.html.heex | 2 +- lib/atomic_web/live/organization_live/show.ex | 14 ++- .../live/partner_live/form_component.ex | 2 + .../partner_live/form_component.html.heex | 2 +- lib/atomic_web/live/partner_live/index.ex | 25 ++-- .../live/partner_live/index.html.heex | 10 +- lib/atomic_web/live/partner_live/show.ex | 16 ++- .../live/partner_live/show.html.heex | 8 +- .../live/speaker_live/form_component.ex | 2 + lib/atomic_web/live/speaker_live/index.ex | 24 ++-- .../live/speaker_live/index.html.heex | 10 +- lib/atomic_web/live/speaker_live/show.ex | 16 ++- .../live/speaker_live/show.html.heex | 8 +- lib/atomic_web/live/user_live/edit.html.heex | 2 +- lib/atomic_web/plugs/authorize.ex | 47 +++++++ lib/atomic_web/router.ex | 111 ++++++++++------- .../templates/layout/root.html.heex | 16 ++- .../2022000000000_create_organizations.exs | 1 + ...0221014155230_create_users_auth_tables.exs | 4 + .../20221129000955_create_speakers.exs | 1 + priv/repo/seeds.exs | 5 +- priv/repo/seeds/accounts.exs | 5 +- priv/repo/seeds/activities.exs | 76 ++++-------- priv/repo/seeds/memberships.exs | 69 +++++++++++ priv/repo/seeds/organizations.exs | 57 --------- test/atomic/activities_test.exs | 7 +- test/atomic/partnerships_test.exs | 9 +- .../controllers/page_controller_test.exs | 3 +- .../user_registration_controller_test.exs | 5 +- .../user_session_controller_test.exs | 3 +- test/atomic_web/live/department_live_test.exs | 3 - .../live/organization_live_test.exs | 117 ------------------ test/atomic_web/live/partner_live_test.exs | 109 ---------------- test/atomic_web/live/speaker_live_test.exs | 112 ----------------- test/support/conn_case.ex | 3 +- test/support/fixtures/accounts_fixtures.ex | 4 +- test/support/fixtures/activities_fixtures.ex | 5 +- .../support/fixtures/partnerships_fixtures.ex | 5 +- 72 files changed, 730 insertions(+), 682 deletions(-) create mode 100644 lib/atomic_web/exceptions.ex create mode 100644 lib/atomic_web/live/home_live/index.ex create mode 100644 lib/atomic_web/live/home_live/index.html.heex create mode 100644 lib/atomic_web/plugs/authorize.ex create mode 100644 priv/repo/seeds/memberships.exs delete mode 100644 test/atomic_web/live/department_live_test.exs delete mode 100644 test/atomic_web/live/organization_live_test.exs delete mode 100644 test/atomic_web/live/partner_live_test.exs delete mode 100644 test/atomic_web/live/speaker_live_test.exs diff --git a/lib/atomic/accounts.ex b/lib/atomic/accounts.ex index 0fd35f28e..4ef52b75d 100644 --- a/lib/atomic/accounts.ex +++ b/lib/atomic/accounts.ex @@ -266,7 +266,9 @@ defmodule Atomic.Accounts do """ def get_user_by_session_token(token) do {:ok, query} = UserToken.verify_session_token_query(token) + Repo.one(query) + |> Repo.preload(:organizations) end @doc """ @@ -424,4 +426,11 @@ defmodule Atomic.Accounts do |> Course.changeset(attrs) |> Repo.insert() end + + @doc """ + Gets the user's organizations + """ + def get_user_organizations(user) do + Repo.all(Ecto.assoc(user, :organizations)) + end end diff --git a/lib/atomic/accounts/user.ex b/lib/atomic/accounts/user.ex index b99913821..bce8add2c 100644 --- a/lib/atomic/accounts/user.ex +++ b/lib/atomic/accounts/user.ex @@ -10,7 +10,7 @@ defmodule Atomic.Accounts.User do alias Atomic.Uploaders.ProfilePicture @required_fields ~w(email password role name)a - @optional_fields ~w(course_id)a + @optional_fields ~w(course_id default_organization_id)a @roles ~w(admin student)a @@ -20,6 +20,7 @@ defmodule Atomic.Accounts.User do field :password, :string, virtual: true, redact: true field :hashed_password, :string, redact: true field :confirmed_at, :naive_datetime + belongs_to :default_organization, Organization belongs_to :course, Course field :profile_picture, ProfilePicture.Type diff --git a/lib/atomic/activities.ex b/lib/atomic/activities.ex index bc9dc1222..29295f958 100644 --- a/lib/atomic/activities.ex +++ b/lib/atomic/activities.ex @@ -22,6 +22,15 @@ defmodule Atomic.Activities do |> Repo.all() end + def list_activities_by_organization_id(organization_id, opts) do + Activity + |> apply_filters(opts) + |> join(:inner, [a], d in assoc(a, :departments)) + |> where([a, d], d.organization_id == ^organization_id) + |> select([a, _d], a) + |> Repo.all() + end + @doc """ Gets a single activity. @@ -41,6 +50,12 @@ defmodule Atomic.Activities do |> Repo.preload(preloads) end + def get_activity_organizations!(activity, _preloads \\ []) do + departments = Map.get(activity, :departments, []) + + Enum.map(departments, & &1.organization_id) + end + alias Atomic.Activities.Enrollment def is_participating?(activity_id, user_id) do @@ -389,6 +404,19 @@ defmodule Atomic.Activities do Repo.all(Speaker) end + @doc """ + Returns the list of speakers belonging to an organization. + + ## Examples + + iex> list_speakers_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621) + [%Speaker{}, ...] + + """ + def list_speakers_by_organization_id(id) do + Repo.all(from s in Speaker, where: s.organization_id == ^id) + end + def get_speakers(nil), do: [] def get_speakers(ids) do diff --git a/lib/atomic/activities/speaker.ex b/lib/atomic/activities/speaker.ex index 20e9da4b9..3dba380c4 100644 --- a/lib/atomic/activities/speaker.ex +++ b/lib/atomic/activities/speaker.ex @@ -5,18 +5,25 @@ defmodule Atomic.Activities.Speaker do use Atomic.Schema alias Atomic.Activities.Activity alias Atomic.Activities.ActivitySpeaker + alias Atomic.Organizations.Organization + + @required_fields ~w(name bio organization_id)a schema "speakers" do field :bio, :string field :name, :string + many_to_many :activities, Activity, join_through: ActivitySpeaker, on_replace: :delete + + belongs_to :organization, Organization, on_replace: :delete_if_exists + timestamps() end @doc false def changeset(speaker, attrs) do speaker - |> cast(attrs, [:name, :bio]) - |> validate_required([:name, :bio]) + |> cast(attrs, @required_fields) + |> validate_required(@required_fields) end end diff --git a/lib/atomic/departments.ex b/lib/atomic/departments.ex index 74237c80c..024435cb4 100644 --- a/lib/atomic/departments.ex +++ b/lib/atomic/departments.ex @@ -21,6 +21,19 @@ defmodule Atomic.Departments do Repo.all(Department) end + @doc """ + Returns the list of departments belonging to an organization. + + ## Examples + + iex> list_departments_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621) + [%Department{}, ...] + + """ + def list_departments_by_organization_id(id) do + Repo.all(from d in Department, where: d.organization_id == ^id) + end + def get_departments(nil), do: [] def get_departments(ids) do diff --git a/lib/atomic/organizations.ex b/lib/atomic/organizations.ex index 26bb50761..a24c6b883 100644 --- a/lib/atomic/organizations.ex +++ b/lib/atomic/organizations.ex @@ -168,6 +168,15 @@ defmodule Atomic.Organizations do |> Repo.exists?() end + def get_role(user_id, organization_id) do + membership = + Membership + |> where([m], m.user_id == ^user_id and m.organization_id == ^organization_id) + |> Repo.one() + + membership[:role] + end + @doc """ Gets a single membership. @@ -247,12 +256,22 @@ defmodule Atomic.Organizations do """ def roles_less_than_or_equal(role) do - case role do - :follower -> [] - :member -> [:member] - :admin -> [:member, :admin] - :owner -> [:member, :admin, :owner] - end + list = [:follower, :member, :admin, :owner] + Enum.drop_while(list, fn elem -> elem != role end) + end + + @doc """ + Returns all roles bigger or equal to the given role. + + ## Examples + + iex> roles_bigger_than_or_equal(:member) + [:member, :admin, :owner] + + """ + def roles_bigger_than_or_equal(role) do + list = [:follower, :member, :admin, :owner] + Enum.drop_while(list, fn elem -> elem != role end) end @doc """ diff --git a/lib/atomic/partnerships.ex b/lib/atomic/partnerships.ex index 0f66f4738..0b9521db6 100644 --- a/lib/atomic/partnerships.ex +++ b/lib/atomic/partnerships.ex @@ -21,6 +21,19 @@ defmodule Atomic.Partnerships do Repo.all(Partner) end + @doc """ + Returns the list of partnerships belonging to an organization. + + ## Examples + + iex> list_partnerships_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621) + [%Partner{}, ...] + + """ + def list_partnerships_by_organization_id(id) do + Repo.all(from p in Partner, where: p.organization_id == ^id) + end + @doc """ Gets a single partner. diff --git a/lib/atomic/partnerships/partner.ex b/lib/atomic/partnerships/partner.ex index 4c5b8be49..e907f6858 100644 --- a/lib/atomic/partnerships/partner.ex +++ b/lib/atomic/partnerships/partner.ex @@ -7,7 +7,7 @@ defmodule Atomic.Partnerships.Partner do alias Atomic.Organizations.Organization alias Atomic.Uploaders - @required_fields ~w(name description)a + @required_fields ~w(name description organization_id)a @optional_fields [] diff --git a/lib/atomic_web/controllers/user_auth.ex b/lib/atomic_web/controllers/user_auth.ex index 134b17634..acae72d8a 100644 --- a/lib/atomic_web/controllers/user_auth.ex +++ b/lib/atomic_web/controllers/user_auth.ex @@ -6,6 +6,7 @@ defmodule AtomicWeb.UserAuth do import Phoenix.Controller alias Atomic.Accounts + alias Atomic.Organizations alias AtomicWeb.Router.Helpers, as: Routes # Make the remember me cookie valid for 60 days. @@ -31,12 +32,27 @@ defmodule AtomicWeb.UserAuth do token = Accounts.generate_user_session_token(user) user_return_to = get_session(conn, :user_return_to) - conn - |> renew_session() - |> put_session(:user_token, token) - |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") - |> maybe_write_remember_me_cookie(token, params) - |> redirect(to: user_return_to || signed_in_path(conn)) + case user.default_organization_id do + nil -> + conn + |> renew_session() + |> put_session(:user_token, token) + |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") + |> maybe_write_remember_me_cookie(token, params) + |> redirect(to: user_return_to || signed_in_path(conn)) + + _ -> + conn + |> renew_session() + |> put_session(:user_token, token) + |> put_session( + :current_organization, + Organizations.get_organization!(user.default_organization_id) + ) + |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") + |> maybe_write_remember_me_cookie(token, params) + |> redirect(to: user_return_to || signed_in_path(conn)) + end end defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do @@ -94,7 +110,9 @@ defmodule AtomicWeb.UserAuth do def fetch_current_user(conn, _opts) do {user_token, conn} = ensure_user_token(conn) user = user_token && Accounts.get_user_by_session_token(user_token) - assign(conn, :current_user, user) + + conn + |> assign(:current_user, user) end defp ensure_user_token(conn) do diff --git a/lib/atomic_web/exceptions.ex b/lib/atomic_web/exceptions.ex new file mode 100644 index 000000000..f5d0f032b --- /dev/null +++ b/lib/atomic_web/exceptions.ex @@ -0,0 +1,4 @@ +defmodule AtomicWeb.MismatchError do + defexception message: "The provided parameters have no relation in the database.", + plug_status: 404 +end diff --git a/lib/atomic_web/live/activity_live/edit.ex b/lib/atomic_web/live/activity_live/edit.ex index 8f63b4003..3d99ded7b 100644 --- a/lib/atomic_web/live/activity_live/edit.ex +++ b/lib/atomic_web/live/activity_live/edit.ex @@ -10,13 +10,19 @@ defmodule AtomicWeb.ActivityLive.Edit do end @impl true - def handle_params(%{"id" => id} = _params, _url, socket) do - {:noreply, - socket - |> assign(:page_title, gettext("Edit Activity")) - |> assign( - :activity, - Activities.get_activity!(id, [:activity_sessions, :speakers, :departments]) - )} + def handle_params(%{"organization_id" => organization_id, "id" => id} = _params, _url, socket) do + activity = + Activities.get_activity!(id, preloads: [:activity_sessions, :departments, :speakers]) + + organizations = Activities.get_activity_organizations!(activity) + + if organization_id in organizations do + {:noreply, + socket + |> assign(:page_title, gettext("Edit Activity")) + |> assign(:activity, activity)} + else + raise AtomicWeb.MismatchError + end end end diff --git a/lib/atomic_web/live/activity_live/edit.html.heex b/lib/atomic_web/live/activity_live/edit.html.heex index 76dc631a2..598333bc2 100644 --- a/lib/atomic_web/live/activity_live/edit.html.heex +++ b/lib/atomic_web/live/activity_live/edit.html.heex @@ -1,3 +1,3 @@
- <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @activity)} /> + <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} organization={@current_organization} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @current_organization, @activity)} />
diff --git a/lib/atomic_web/live/activity_live/form_component.ex b/lib/atomic_web/live/activity_live/form_component.ex index 2c246194b..a237b7d20 100644 --- a/lib/atomic_web/live/activity_live/form_component.ex +++ b/lib/atomic_web/live/activity_live/form_component.ex @@ -7,25 +7,35 @@ defmodule AtomicWeb.ActivityLive.FormComponent do @impl true def mount(socket) do - departments = Departments.list_departments() - speakers = Activities.list_speakers() - {:ok, socket - |> assign(:departments, departments) - |> assign(:speakers, speakers)} + |> assign(:departments, []) + |> assign(:speakers, [])} end @impl true def update(%{activity: activity} = assigns, socket) do + departments = Departments.list_departments_by_organization_id(assigns.organization.id) + speakers = Activities.list_speakers_by_organization_id(assigns.organization.id) changeset = Activities.change_activity(activity) {:ok, socket |> assign(assigns) + |> assign(:departments, departments) + |> assign(:speakers, speakers) |> assign(:changeset, changeset)} end + @impl true + def update(%{"organization_id" => organization_id}, socket) do + departments = Departments.list_departments_by_organization_id(organization_id) + + {:ok, + socket + |> assign(:departments, departments)} + end + @impl true def handle_event("validate", %{"activity" => activity_params}, socket) do changeset = diff --git a/lib/atomic_web/live/activity_live/index.ex b/lib/atomic_web/live/activity_live/index.ex index 3dcf0321e..341a2abb9 100644 --- a/lib/atomic_web/live/activity_live/index.ex +++ b/lib/atomic_web/live/activity_live/index.ex @@ -5,8 +5,8 @@ defmodule AtomicWeb.ActivityLive.Index do alias Atomic.Activities.Activity @impl true - def mount(_params, _session, socket) do - {:ok, assign(socket, :activities, list_activities())} + def mount(params, _session, socket) do + {:ok, assign(socket, :activities, list_activities(params["organization_id"]))} end @impl true @@ -37,10 +37,10 @@ defmodule AtomicWeb.ActivityLive.Index do activity = Activities.get_activity!(id) {:ok, _} = Activities.delete_activity(activity) - {:noreply, assign(socket, :activies, list_activities())} + {:noreply, assign(socket, :activies, list_activities(socket.assigns.current_organization.id))} end - defp list_activities do - Activities.list_activities(preloads: [:activity_sessions]) + defp list_activities(organization_id) do + Activities.list_activities_by_organization_id(organization_id, preloads: [:activity_sessions]) end end diff --git a/lib/atomic_web/live/activity_live/index.html.heex b/lib/atomic_web/live/activity_live/index.html.heex index 2b959a11f..6cdc03b3f 100644 --- a/lib/atomic_web/live/activity_live/index.html.heex +++ b/lib/atomic_web/live/activity_live/index.html.heex @@ -22,11 +22,11 @@ <%= activity.maximum_entries %> - <%= live_redirect("Show", to: Routes.activity_show_path(@socket, :show, activity)) %> + <%= live_redirect("Show", to: Routes.activity_show_path(@socket, :show, @current_organization, activity)) %> <% end %> -<%= live_patch("New Activity", to: Routes.activity_new_path(@socket, :new)) %> +<%= live_patch("New Activity", to: Routes.activity_new_path(@socket, :new, @current_organization)) %> diff --git a/lib/atomic_web/live/activity_live/new.html.heex b/lib/atomic_web/live/activity_live/new.html.heex index b6676be95..0cd31fb2a 100644 --- a/lib/atomic_web/live/activity_live/new.html.heex +++ b/lib/atomic_web/live/activity_live/new.html.heex @@ -1,3 +1,3 @@
- <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={:new} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_index_path(@socket, :index)} /> + <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={:new} organization={@current_organization} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_index_path(@socket, :index, @current_organization)} />
diff --git a/lib/atomic_web/live/activity_live/show.ex b/lib/atomic_web/live/activity_live/show.ex index 7f2b21ccc..78b6ca9e5 100644 --- a/lib/atomic_web/live/activity_live/show.ex +++ b/lib/atomic_web/live/activity_live/show.ex @@ -14,14 +14,19 @@ defmodule AtomicWeb.ActivityLive.Show do end @impl true - def handle_params(%{"id" => id}, _, socket) do + def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do activity = Activities.get_activity!(id, [:activity_sessions, :departments, :speakers]) + organizations = Activities.get_activity_organizations!(activity) - {:noreply, - socket - |> assign(:enrolled?, Activities.is_user_enrolled?(activity, socket.assigns.current_user)) - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:activity, %{activity | enrolled: Activities.get_total_enrolled(activity)})} + if organization_id in organizations do + {:noreply, + socket + |> assign(:enrolled?, Activities.is_user_enrolled?(activity, socket.assigns.current_user)) + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:activity, %{activity | enrolled: Activities.get_total_enrolled(activity)})} + else + raise AtomicWeb.MismatchError + end end @impl true @@ -68,7 +73,10 @@ defmodule AtomicWeb.ActivityLive.Show do def handle_event("delete", _payload, socket) do {:ok, _} = Activities.delete_activity(socket.assigns.activity) - {:noreply, push_redirect(socket, to: Routes.activity_index_path(socket, :index))} + {:noreply, + push_redirect(socket, + to: Routes.activity_index_path(socket, :index, socket.assigns.current_organization) + )} end @impl true diff --git a/lib/atomic_web/live/activity_live/show.html.heex b/lib/atomic_web/live/activity_live/show.html.heex index bef7e0824..2648b0ff8 100644 --- a/lib/atomic_web/live/activity_live/show.html.heex +++ b/lib/atomic_web/live/activity_live/show.html.heex @@ -1,8 +1,8 @@

Show Activity

<%= if @live_action in [:edit] do %> - <.modal return_to={Routes.activity_show_path(@socket, :show, @activity)}> - <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @activity)} /> + <.modal return_to={Routes.activity_show_path(@socket, :show, @current_organization, @activity)}> + <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} organization={@current_organization} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @current_organization, @activity)} /> <% end %> @@ -94,6 +94,6 @@ <% end %> <%= if @current_user.role in [:admin] do %> - <%= live_patch("Edit", to: Routes.activity_edit_path(@socket, :edit, @activity), class: "button") %> | <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: @activity.id, data: [confirm: "Are you sure?"]) %> | - <%= live_redirect("Back", to: Routes.activity_index_path(@socket, :index)) %> + <%= live_patch("Edit", to: Routes.activity_edit_path(@socket, :edit, @activity, @current_organization), class: "button") %> | <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: @activity.id, data: [confirm: "Are you sure?"]) %> | + <%= live_redirect("Back", to: Routes.activity_index_path(@socket, :index, @current_organization)) %> <% end %> diff --git a/lib/atomic_web/live/board_live/edit.ex b/lib/atomic_web/live/board_live/edit.ex index 748a7543f..dfe1c3282 100644 --- a/lib/atomic_web/live/board_live/edit.ex +++ b/lib/atomic_web/live/board_live/edit.ex @@ -10,16 +10,20 @@ defmodule AtomicWeb.BoardLive.Edit do end @impl true - def handle_params(%{"org" => _org, "id" => id}, _url, socket) do + def handle_params(%{"organization_id" => organization_id, "id" => id}, _url, socket) do user_organization = Organizations.get_user_organization!(id, [:user, :organization]) users = Enum.map(Accounts.list_users(), fn u -> [key: u.email, value: u.id] end) - {:noreply, - socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:user_organization, user_organization) - |> assign(:users, users) - |> assign(:current_user, socket.assigns.current_user)} + if user_organization.organization_id == organization_id do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:user_organization, user_organization) + |> assign(:users, users) + |> assign(:current_user, socket.assigns.current_user)} + else + raise AtomicWeb.MismatchError + end end defp page_title(:index), do: "List board" diff --git a/lib/atomic_web/live/board_live/index.ex b/lib/atomic_web/live/board_live/index.ex index d56a8df80..b42730975 100644 --- a/lib/atomic_web/live/board_live/index.ex +++ b/lib/atomic_web/live/board_live/index.ex @@ -9,7 +9,7 @@ defmodule AtomicWeb.BoardLive.Index do end @impl true - def handle_params(%{"org" => id}, _, socket) do + def handle_params(%{"organization_id" => id}, _, socket) do users_organizations = list_users_organizations(id) {:noreply, diff --git a/lib/atomic_web/live/board_live/new.ex b/lib/atomic_web/live/board_live/new.ex index bf42259aa..e7aee6840 100644 --- a/lib/atomic_web/live/board_live/new.ex +++ b/lib/atomic_web/live/board_live/new.ex @@ -11,12 +11,12 @@ defmodule AtomicWeb.BoardLive.New do end @impl true - def handle_params(%{"org" => org_id}, _url, socket) do + def handle_params(%{"organization_id" => organization_id}, _url, socket) do {:noreply, socket |> assign(:page_title, gettext("New Board")) |> assign(:user_organization, %UserOrganization{ - organization_id: org_id + organization_id: organization_id }) |> assign(:users, Enum.map(Accounts.list_users(), fn u -> [key: u.email, value: u.id] end)) |> assign(:current_user, socket.assigns.current_user)} diff --git a/lib/atomic_web/live/board_live/show.ex b/lib/atomic_web/live/board_live/show.ex index 3e0d43d2d..e9c860d27 100644 --- a/lib/atomic_web/live/board_live/show.ex +++ b/lib/atomic_web/live/board_live/show.ex @@ -9,13 +9,17 @@ defmodule AtomicWeb.BoardLive.Show do end @impl true - def handle_params(%{"id" => id}, _, socket) do - user_org = Organizations.get_user_organization!(id, [:user, :organization]) + def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do + user_organization = Organizations.get_user_organization!(id, [:user, :organization]) - {:noreply, - socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:user_organization, user_org)} + if user_organization.organization_id == organization_id do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:user_organization, user_organization)} + else + raise AtomicWeb.MismatchError + end end defp page_title(:show), do: "Show Board" diff --git a/lib/atomic_web/live/department_live/index.ex b/lib/atomic_web/live/department_live/index.ex index c95dcbdaf..6f9abf7dc 100644 --- a/lib/atomic_web/live/department_live/index.ex +++ b/lib/atomic_web/live/department_live/index.ex @@ -3,35 +3,38 @@ defmodule AtomicWeb.DepartmentLive.Index do alias Atomic.Departments alias Atomic.Departments.Department - alias Atomic.Organizations @impl true - def mount(_params, _session, socket) do - {:ok, assign(socket, :departments, list_departments())} + def mount(%{"organization_id" => organization_id}, _session, socket) do + {:ok, assign(socket, :departments, list_departments(organization_id))} end @impl true - def handle_params(%{"org" => _id} = params, _url, socket) do + def handle_params(params, _url, socket) do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end - defp apply_action(socket, :edit, %{"id" => id}) do - socket - |> assign(:page_title, "Edit Department") - |> assign(:department, Departments.get_department!(id)) + defp apply_action(socket, :edit, %{"organization_id" => organization_id, "id" => id}) do + department = Departments.get_department!(id) + + if department.organization_id == organization_id do + socket + |> assign(:page_title, "Edit Department") + |> assign(:department, Departments.get_department!(id)) + else + raise AtomicWeb.MismatchError + end end - defp apply_action(socket, :new, %{"org" => id}) do + defp apply_action(socket, :new, _params) do socket |> assign(:page_title, "New Department") - |> assign(:organization, Organizations.get_organization!(id)) |> assign(:department, %Department{}) end - defp apply_action(socket, :index, %{"org" => id}) do + defp apply_action(socket, :index, _params) do socket |> assign(:page_title, "Listing Departments") - |> assign(:organization, Organizations.get_organization!(id)) |> assign(:department, nil) end @@ -40,10 +43,11 @@ defmodule AtomicWeb.DepartmentLive.Index do department = Departments.get_department!(id) {:ok, _} = Departments.delete_department(department) - {:noreply, assign(socket, :departments, list_departments())} + {:noreply, + assign(socket, :departments, list_departments(socket.assigns.current_organization.id))} end - defp list_departments do - Departments.list_departments() + defp list_departments(id) do + Departments.list_departments_by_organization_id(id) end end diff --git a/lib/atomic_web/live/department_live/index.html.heex b/lib/atomic_web/live/department_live/index.html.heex index abc44402d..580ac0fe4 100644 --- a/lib/atomic_web/live/department_live/index.html.heex +++ b/lib/atomic_web/live/department_live/index.html.heex @@ -1,8 +1,8 @@

Listing Departments

<%= if @live_action in [:new, :edit] do %> - <.modal return_to={Routes.department_index_path(@socket, :index, @organization)}> - <.live_component module={AtomicWeb.DepartmentLive.FormComponent} organization={@organization} id={@department.id || :new} title={@page_title} action={@live_action} department={@department} return_to={Routes.department_index_path(@socket, :index, @organization)} /> + <.modal return_to={Routes.department_index_path(@socket, :index, @current_organization)}> + <.live_component module={AtomicWeb.DepartmentLive.FormComponent} id={@department.id || :new} organization={@current_organization} title={@page_title} action={@live_action} department={@department} return_to={Routes.department_index_path(@socket, :index, @current_organization)} /> <% end %> @@ -20,8 +20,8 @@ <%= department.name %> - <%= live_redirect("Show", to: Routes.department_show_path(@socket, :show, department.organization_id, department)) %> - <%= live_patch("Edit", to: Routes.department_index_path(@socket, :edit, department.organization_id, department)) %> + <%= live_redirect("Show", to: Routes.department_show_path(@socket, :show, @current_organization, department)) %> + <%= live_patch("Edit", to: Routes.department_index_path(@socket, :edit, @current_organization, department)) %> <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: department.id, data: [confirm: "Are you sure?"]) %> @@ -29,4 +29,4 @@ -<%= live_patch("New Department", to: Routes.department_index_path(@socket, :new, @organization)) %> +<%= live_patch("New Department", to: Routes.department_index_path(@socket, :new, @current_organization)) %> diff --git a/lib/atomic_web/live/department_live/show.ex b/lib/atomic_web/live/department_live/show.ex index e8ff68123..a70088ee6 100644 --- a/lib/atomic_web/live/department_live/show.ex +++ b/lib/atomic_web/live/department_live/show.ex @@ -2,7 +2,6 @@ defmodule AtomicWeb.DepartmentLive.Show do use AtomicWeb, :live_view alias Atomic.Departments - alias Atomic.Organizations @impl true def mount(_params, _session, socket) do @@ -10,12 +9,17 @@ defmodule AtomicWeb.DepartmentLive.Show do end @impl true - def handle_params(%{"org" => org, "id" => id}, _, socket) do - {:noreply, - socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:organization, Organizations.get_organization!(org)) - |> assign(:department, Departments.get_department!(id, preloads: :activities))} + def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do + department = Departments.get_department!(id, preloads: [:activities]) + + if department.organization_id == organization_id do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:department, department)} + else + raise AtomicWeb.MismatchError + end end defp page_title(:show), do: "Show Department" diff --git a/lib/atomic_web/live/department_live/show.html.heex b/lib/atomic_web/live/department_live/show.html.heex index 430a0c233..a48601716 100644 --- a/lib/atomic_web/live/department_live/show.html.heex +++ b/lib/atomic_web/live/department_live/show.html.heex @@ -1,8 +1,8 @@

Show Department

<%= if @live_action in [:edit] do %> - <.modal return_to={Routes.department_show_path(@socket, :show, @organization, @department)}> - <.live_component module={AtomicWeb.DepartmentLive.FormComponent} id={@department.id} title={@page_title} action={@live_action} department={@department} return_to={Routes.department_show_path(@socket, :show, @organization, @department)} /> + <.modal return_to={Routes.department_show_path(@socket, :show, @current_organization, @department)}> + <.live_component module={AtomicWeb.DepartmentLive.FormComponent} id={@department.id} title={@page_title} action={@live_action} department={@department} return_to={Routes.department_show_path(@socket, :show, @current_organization, @department)} /> <% end %> @@ -17,12 +17,12 @@ -<%= live_patch("Edit", to: Routes.department_show_path(@socket, :edit, @organization, @department), class: "button") %> | -<%= live_redirect("Back", to: Routes.department_index_path(@socket, :index, @organization)) %> +<%= live_patch("Edit", to: Routes.department_show_path(@socket, :edit, @current_organization, @department), class: "button") %> | +<%= live_redirect("Back", to: Routes.department_index_path(@socket, :index, @current_organization)) %> diff --git a/lib/atomic_web/live/home_live/index.ex b/lib/atomic_web/live/home_live/index.ex new file mode 100644 index 000000000..f0551b825 --- /dev/null +++ b/lib/atomic_web/live/home_live/index.ex @@ -0,0 +1,16 @@ +defmodule AtomicWeb.HomeLive.Index do + @moduledoc false + use AtomicWeb, :live_view + + @impl true + def mount(_params, _session, socket) do + {:ok, socket} + end + + @impl true + def handle_params(_params, _url, socket) do + {:noreply, + socket + |> assign(:page_title, "Home")} + end +end diff --git a/lib/atomic_web/live/home_live/index.html.heex b/lib/atomic_web/live/home_live/index.html.heex new file mode 100644 index 000000000..719de6cd4 --- /dev/null +++ b/lib/atomic_web/live/home_live/index.html.heex @@ -0,0 +1,3 @@ +
+

Atomic Home

+
diff --git a/lib/atomic_web/live/hooks.ex b/lib/atomic_web/live/hooks.ex index b9add6517..5a7548ec4 100644 --- a/lib/atomic_web/live/hooks.ex +++ b/lib/atomic_web/live/hooks.ex @@ -5,15 +5,74 @@ defmodule AtomicWeb.Hooks do import Phoenix.LiveView alias Atomic.Accounts + alias Atomic.Organizations def on_mount(:default, _params, _session, socket) do {:cont, assign(socket, :page_title, "Atomic")} end - def on_mount(:current_user, _params, %{"user_token" => user_token}, socket) do + def on_mount( + :authenticated_user_state, + _params, + %{"user_token" => user_token, "current_organization" => _}, + socket + ) do current_user = Accounts.get_user_by_session_token(user_token) - {:cont, assign(socket, current_user: current_user)} + if current_user.default_organization_id != nil do + socket = + socket + |> assign(:current_user, current_user) + |> assign( + :current_organization, + Organizations.get_organization!(current_user.default_organization_id) + ) + + {:cont, socket} + else + socket = + socket + |> assign(:current_user, current_user) + + {:cont, socket} + end + end + + def on_mount(:general_user_state, _params, session, socket) do + current_organization = session["current_organization"] + current_user = session["user_token"] + + case {current_organization, current_user} do + {nil, nil} -> + {:cont, socket} + + {nil, _} -> + user = Accounts.get_user_by_session_token(current_user) + + {:cont, + socket + |> assign(:current_user, user) + |> assign( + :current_organization, + Organizations.get_organization!(user.default_organization_id) + )} + + {_, nil} -> + {:cont, + socket + |> assign(:current_organization, current_organization)} + + {_, _} -> + user = Accounts.get_user_by_session_token(current_user) + + {:cont, + socket + |> assign(:current_user, user) + |> assign( + :current_organization, + Organizations.get_organization!(user.default_organization_id) + )} + end end def on_mount(:current_user, _params, _session, socket) do diff --git a/lib/atomic_web/live/membership_live/edit.ex b/lib/atomic_web/live/membership_live/edit.ex index b12c0edf0..bb418b48e 100644 --- a/lib/atomic_web/live/membership_live/edit.ex +++ b/lib/atomic_web/live/membership_live/edit.ex @@ -9,19 +9,23 @@ defmodule AtomicWeb.MembershipLive.Edit do end @impl true - def handle_params(%{"org" => org, "id" => id}, _, socket) do + def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do membership = Organizations.get_membership!(id, [:user, :organization, :created_by]) - {:noreply, - socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:organization, org) - |> assign(:membership, membership) - |> assign(:current_user, socket.assigns.current_user) - |> assign( - :allowed_roles, - Organizations.roles_less_than_or_equal(socket.assigns.current_user.role) - )} + if membership.organization_id == organization_id do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:organization, organization_id) + |> assign(:membership, membership) + |> assign(:current_user, socket.assigns.current_user) + |> assign( + :allowed_roles, + Organizations.roles_less_than_or_equal(socket.assigns.current_user.role) + )} + else + raise AtomicWeb.MismatchError + end end defp page_title(:index), do: "List memberships" diff --git a/lib/atomic_web/live/membership_live/index.ex b/lib/atomic_web/live/membership_live/index.ex index 454c68208..b745058e3 100644 --- a/lib/atomic_web/live/membership_live/index.ex +++ b/lib/atomic_web/live/membership_live/index.ex @@ -9,7 +9,7 @@ defmodule AtomicWeb.MembershipLive.Index do end @impl true - def handle_params(%{"org" => id}, _, socket) do + def handle_params(%{"organization_id" => id}, _, socket) do memberships = Organizations.list_memberships(%{"organization_id" => id}, [:user]) |> Enum.filter(fn m -> m.role != :follower end) diff --git a/lib/atomic_web/live/membership_live/new.ex b/lib/atomic_web/live/membership_live/new.ex index 020fe7df3..bbe906dd5 100644 --- a/lib/atomic_web/live/membership_live/new.ex +++ b/lib/atomic_web/live/membership_live/new.ex @@ -12,12 +12,12 @@ defmodule AtomicWeb.MembershipLive.New do end @impl true - def handle_params(%{"org" => org_id}, _url, socket) do + def handle_params(%{"organization_id" => organization_id}, _url, socket) do {:noreply, socket |> assign(:page_title, gettext("New Membership")) |> assign(:membership, %Membership{ - organization_id: org_id + organization_id: organization_id }) |> assign(:users, Enum.map(Accounts.list_users(), fn u -> [key: u.email, value: u.id] end)) |> assign( diff --git a/lib/atomic_web/live/membership_live/show.ex b/lib/atomic_web/live/membership_live/show.ex index fab8a6238..9b2e1c42b 100644 --- a/lib/atomic_web/live/membership_live/show.ex +++ b/lib/atomic_web/live/membership_live/show.ex @@ -9,13 +9,17 @@ defmodule AtomicWeb.MembershipLive.Show do end @impl true - def handle_params(%{"org" => _org, "id" => id}, _, socket) do + def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do membership = Organizations.get_membership!(id, [:user, :organization, :created_by]) - {:noreply, - socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:membership, membership)} + if membership.organization_id == organization_id do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:membership, membership)} + else + raise AtomicWeb.MismatchError + end end defp page_title(:index), do: "List memberships" diff --git a/lib/atomic_web/live/organization_live/index.ex b/lib/atomic_web/live/organization_live/index.ex index 8ecb9635f..65bc18795 100644 --- a/lib/atomic_web/live/organization_live/index.ex +++ b/lib/atomic_web/live/organization_live/index.ex @@ -1,12 +1,18 @@ defmodule AtomicWeb.OrganizationLive.Index do use AtomicWeb, :live_view + alias Atomic.Accounts alias Atomic.Organizations alias Atomic.Organizations.Organization @impl true - def mount(_params, _session, socket) do - {:ok, assign(socket, :organizations, list_organizations())} + def mount(_params, session, socket) do + user = Accounts.get_user_by_session_token(session["user_token"]) + + {:ok, + socket + |> assign(:organizations, list_organizations()) + |> assign(:current_user, user)} end @impl true @@ -14,7 +20,7 @@ defmodule AtomicWeb.OrganizationLive.Index do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end - defp apply_action(socket, :edit, %{"id" => id}) do + defp apply_action(socket, :edit, %{"organization_id" => id}) do socket |> assign(:page_title, "Edit Organization") |> assign(:organization, Organizations.get_organization!(id)) @@ -33,7 +39,7 @@ defmodule AtomicWeb.OrganizationLive.Index do end @impl true - def handle_event("delete", %{"id" => id}, socket) do + def handle_event("delete", %{"organization_id" => id}, socket) do organization = Organizations.get_organization!(id) {:ok, _} = Organizations.delete_organization(organization) @@ -43,4 +49,8 @@ defmodule AtomicWeb.OrganizationLive.Index do defp list_organizations do Organizations.list_organizations() end + + def update_default_organization(user, organization) do + Accounts.update_user(user, %{default_organization_id: organization.id}) + end end diff --git a/lib/atomic_web/live/organization_live/index.html.heex b/lib/atomic_web/live/organization_live/index.html.heex index 7f68e2ac1..159e18353 100644 --- a/lib/atomic_web/live/organization_live/index.html.heex +++ b/lib/atomic_web/live/organization_live/index.html.heex @@ -22,7 +22,7 @@ <%= organization.description %> - <%= live_redirect("Show", to: Routes.organization_show_path(@socket, :show, organization)) %> + <%= live_patch("Show", to: Routes.organization_show_path(@socket, :show, organization)) %> <%= live_patch("Edit", to: Routes.organization_index_path(@socket, :edit, organization)) %> <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: organization.id, data: [confirm: "Are you sure?"]) %> diff --git a/lib/atomic_web/live/organization_live/show.ex b/lib/atomic_web/live/organization_live/show.ex index 4b5122e24..ccb8ba418 100644 --- a/lib/atomic_web/live/organization_live/show.ex +++ b/lib/atomic_web/live/organization_live/show.ex @@ -1,16 +1,24 @@ defmodule AtomicWeb.OrganizationLive.Show do use AtomicWeb, :live_view + alias Atomic.Accounts alias Atomic.Organizations @impl true - def mount(_params, _session, socket) do - {:ok, socket} + def mount(_params, session, socket) do + user = Accounts.get_user_by_session_token(session["user_token"]) + + {:ok, socket |> assign(:current_user, user)} end @impl true - def handle_params(%{"id" => id}, _, socket) do + def handle_params(%{"organization_id" => id}, _, socket) do org = Organizations.get_organization!(id, [:departments]) + user = socket.assigns.current_user + + if user.default_organization_id != id do + Accounts.update_user(user, %{"default_organization_id" => id}) + end {:noreply, socket diff --git a/lib/atomic_web/live/partner_live/form_component.ex b/lib/atomic_web/live/partner_live/form_component.ex index 64fb33643..6ee1d0d97 100644 --- a/lib/atomic_web/live/partner_live/form_component.ex +++ b/lib/atomic_web/live/partner_live/form_component.ex @@ -57,6 +57,8 @@ defmodule AtomicWeb.PartnerLive.FormComponent do end defp save_partner(socket, :new, partner_params) do + partner_params = Map.put(partner_params, "organization_id", socket.assigns.organization.id) + case Partnerships.create_partner(partner_params, &consume_image_data(socket, &1)) do {:ok, _partner} -> {:noreply, diff --git a/lib/atomic_web/live/partner_live/form_component.html.heex b/lib/atomic_web/live/partner_live/form_component.html.heex index 26d70a1d7..731c5b1fe 100644 --- a/lib/atomic_web/live/partner_live/form_component.html.heex +++ b/lib/atomic_web/live/partner_live/form_component.html.heex @@ -72,7 +72,7 @@ <%= submit do %> -
+
Save
<% end %> diff --git a/lib/atomic_web/live/partner_live/index.ex b/lib/atomic_web/live/partner_live/index.ex index 01b517731..b1937ef67 100644 --- a/lib/atomic_web/live/partner_live/index.ex +++ b/lib/atomic_web/live/partner_live/index.ex @@ -5,8 +5,8 @@ defmodule AtomicWeb.PartnerLive.Index do alias Atomic.Partnerships.Partner @impl true - def mount(_params, _session, socket) do - {:ok, assign(socket, :partnerships, list_partnerships())} + def mount(%{"organization_id" => organization_id}, _session, socket) do + {:ok, assign(socket, :partnerships, list_partnerships(organization_id))} end @impl true @@ -14,10 +14,16 @@ defmodule AtomicWeb.PartnerLive.Index do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end - defp apply_action(socket, :edit, %{"id" => id}) do - socket - |> assign(:page_title, "Edit Partner") - |> assign(:partner, Partnerships.get_partner!(id)) + defp apply_action(socket, :edit, %{"organization_id" => organization_id, "id" => id}) do + partner = Partnerships.get_partner!(id) + + if partner.organization_id == organization_id do + socket + |> assign(:page_title, "Edit Partner") + |> assign(:partner, Partnerships.get_partner!(id)) + else + raise AtomicWeb.MismatchError + end end defp apply_action(socket, :new, _params) do @@ -37,10 +43,11 @@ defmodule AtomicWeb.PartnerLive.Index do partner = Partnerships.get_partner!(id) {:ok, _} = Partnerships.delete_partner(partner) - {:noreply, assign(socket, :partnerships, list_partnerships())} + {:noreply, + assign(socket, :partnerships, list_partnerships(socket.assigns.current_organization.id))} end - defp list_partnerships do - Partnerships.list_partnerships() + defp list_partnerships(id) do + Partnerships.list_partnerships_by_organization_id(id) end end diff --git a/lib/atomic_web/live/partner_live/index.html.heex b/lib/atomic_web/live/partner_live/index.html.heex index 7ab2cf743..7f892ded7 100644 --- a/lib/atomic_web/live/partner_live/index.html.heex +++ b/lib/atomic_web/live/partner_live/index.html.heex @@ -1,8 +1,8 @@

Listing Partnerships

<%= if @live_action in [:new, :edit] do %> - <.modal return_to={Routes.partner_index_path(@socket, :index)}> - <.live_component module={AtomicWeb.PartnerLive.FormComponent} id={@partner.id || :new} title={@page_title} action={@live_action} partner={@partner} return_to={Routes.partner_index_path(@socket, :index)} /> + <.modal return_to={Routes.partner_index_path(@socket, :index, @current_organization)}> + <.live_component module={AtomicWeb.PartnerLive.FormComponent} id={@partner.id || :new} organization={@current_organization} title={@page_title} action={@live_action} partner={@partner} return_to={Routes.partner_index_path(@socket, :index, @current_organization)} /> <% end %> @@ -22,8 +22,8 @@ <%= partner.description %> - <%= live_redirect("Show", to: Routes.partner_show_path(@socket, :show, partner)) %> - <%= live_patch("Edit", to: Routes.partner_index_path(@socket, :edit, partner)) %> + <%= live_redirect("Show", to: Routes.partner_show_path(@socket, :show, @current_organization, partner)) %> + <%= live_patch("Edit", to: Routes.partner_index_path(@socket, :edit, @current_organization, partner)) %> <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: partner.id, data: [confirm: "Are you sure?"]) %> @@ -31,4 +31,4 @@ -<%= live_patch("New Partner", to: Routes.partner_index_path(@socket, :new)) %> +<%= live_patch("New Partner", to: Routes.partner_index_path(@socket, :new, @current_organization)) %> diff --git a/lib/atomic_web/live/partner_live/show.ex b/lib/atomic_web/live/partner_live/show.ex index 182ad97b5..b8a4701f9 100644 --- a/lib/atomic_web/live/partner_live/show.ex +++ b/lib/atomic_web/live/partner_live/show.ex @@ -10,11 +10,17 @@ defmodule AtomicWeb.PartnerLive.Show do end @impl true - def handle_params(%{"id" => id}, _, socket) do - {:noreply, - socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:partner, Partnerships.get_partner!(id))} + def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do + partner = Partnerships.get_partner!(id) + + if partner.organization_id == organization_id do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:partner, partner)} + else + raise AtomicWeb.MismatchError + end end defp page_title(:show), do: "Show Partner" diff --git a/lib/atomic_web/live/partner_live/show.html.heex b/lib/atomic_web/live/partner_live/show.html.heex index 4f5f51268..daff0ce2e 100644 --- a/lib/atomic_web/live/partner_live/show.html.heex +++ b/lib/atomic_web/live/partner_live/show.html.heex @@ -1,8 +1,8 @@

Show Partner

<%= if @live_action in [:edit] do %> - <.modal return_to={Routes.partner_show_path(@socket, :show, @partner)}> - <.live_component module={AtomicWeb.PartnerLive.FormComponent} id={@partner.id} title={@page_title} action={@live_action} partner={@partner} return_to={Routes.partner_show_path(@socket, :show, @partner)} /> + <.modal return_to={Routes.partner_show_path(@socket, :show, @current_organization, @partner)}> + <.live_component module={AtomicWeb.PartnerLive.FormComponent} id={@partner.id} title={@page_title} action={@live_action} partner={@partner} return_to={Routes.partner_show_path(@socket, :show, @current_organization, @partner)} /> <% end %> @@ -24,5 +24,5 @@ <% end %> -<%= live_patch("Edit", to: Routes.partner_show_path(@socket, :edit, @partner), class: "button") %> | -<%= live_redirect("Back", to: Routes.partner_index_path(@socket, :index)) %> +<%= live_patch("Edit", to: Routes.partner_show_path(@socket, :edit, @current_organization, @partner), class: "button") %> | +<%= live_redirect("Back", to: Routes.partner_index_path(@socket, :index, @current_organization)) %> diff --git a/lib/atomic_web/live/speaker_live/form_component.ex b/lib/atomic_web/live/speaker_live/form_component.ex index ef8bdeab0..144e7a59d 100644 --- a/lib/atomic_web/live/speaker_live/form_component.ex +++ b/lib/atomic_web/live/speaker_live/form_component.ex @@ -41,6 +41,8 @@ defmodule AtomicWeb.SpeakerLive.FormComponent do end defp save_speaker(socket, :new, speaker_params) do + speaker_params = Map.put(speaker_params, "organization_id", socket.assigns.organization.id) + case Activities.create_speaker(speaker_params) do {:ok, _speaker} -> {:noreply, diff --git a/lib/atomic_web/live/speaker_live/index.ex b/lib/atomic_web/live/speaker_live/index.ex index 2157c7bfe..d161ad039 100644 --- a/lib/atomic_web/live/speaker_live/index.ex +++ b/lib/atomic_web/live/speaker_live/index.ex @@ -5,8 +5,8 @@ defmodule AtomicWeb.SpeakerLive.Index do alias Atomic.Activities.Speaker @impl true - def mount(_params, _session, socket) do - {:ok, assign(socket, :speakers, list_speakers())} + def mount(%{"organization_id" => organization_id}, _session, socket) do + {:ok, assign(socket, :speakers, list_speakers(organization_id))} end @impl true @@ -14,10 +14,16 @@ defmodule AtomicWeb.SpeakerLive.Index do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end - defp apply_action(socket, :edit, %{"id" => id}) do - socket - |> assign(:page_title, "Edit Speaker") - |> assign(:speaker, Activities.get_speaker!(id)) + defp apply_action(socket, :edit, %{"organization_id" => organization_id, "id" => id}) do + speaker = Activities.get_speaker!(id) + + if speaker.organization_id == organization_id do + socket + |> assign(:page_title, "Edit Speaker") + |> assign(:speaker, speaker) + else + raise AtomicWeb.MismatchError + end end defp apply_action(socket, :new, _params) do @@ -37,10 +43,10 @@ defmodule AtomicWeb.SpeakerLive.Index do speaker = Activities.get_speaker!(id) {:ok, _} = Activities.delete_speaker(speaker) - {:noreply, assign(socket, :speakers, list_speakers())} + {:noreply, assign(socket, :speakers, list_speakers(socket.assigns.current_organization.id))} end - defp list_speakers do - Activities.list_speakers() + defp list_speakers(organization_id) do + Activities.list_speakers_by_organization_id(organization_id) end end diff --git a/lib/atomic_web/live/speaker_live/index.html.heex b/lib/atomic_web/live/speaker_live/index.html.heex index 17ffbc9c9..ed87b8f1b 100644 --- a/lib/atomic_web/live/speaker_live/index.html.heex +++ b/lib/atomic_web/live/speaker_live/index.html.heex @@ -1,8 +1,8 @@

Listing Speakers

<%= if @live_action in [:new, :edit] do %> - <.modal return_to={Routes.speaker_index_path(@socket, :index)}> - <.live_component module={AtomicWeb.SpeakerLive.FormComponent} id={@speaker.id || :new} title={@page_title} action={@live_action} speaker={@speaker} return_to={Routes.speaker_index_path(@socket, :index)} /> + <.modal return_to={Routes.speaker_index_path(@socket, :index, @current_organization)}> + <.live_component module={AtomicWeb.SpeakerLive.FormComponent} id={@speaker.id || :new} organization={@current_organization} title={@page_title} action={@live_action} speaker={@speaker} return_to={Routes.speaker_index_path(@socket, :index, @current_organization)} /> <% end %> @@ -22,8 +22,8 @@ <%= speaker.bio %> - <%= live_redirect("Show", to: Routes.speaker_show_path(@socket, :show, speaker)) %> - <%= live_patch("Edit", to: Routes.speaker_index_path(@socket, :edit, speaker)) %> + <%= live_redirect("Show", to: Routes.speaker_show_path(@socket, :show, @current_organization, speaker)) %> + <%= live_patch("Edit", to: Routes.speaker_index_path(@socket, :edit, @current_organization, speaker)) %> <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: speaker.id, data: [confirm: "Are you sure?"]) %> @@ -31,4 +31,4 @@ -<%= live_patch("New Speaker", to: Routes.speaker_index_path(@socket, :new)) %> +<%= live_patch("New Speaker", to: Routes.speaker_index_path(@socket, :new, @current_organization)) %> diff --git a/lib/atomic_web/live/speaker_live/show.ex b/lib/atomic_web/live/speaker_live/show.ex index c134819c2..167bb5ded 100644 --- a/lib/atomic_web/live/speaker_live/show.ex +++ b/lib/atomic_web/live/speaker_live/show.ex @@ -9,11 +9,17 @@ defmodule AtomicWeb.SpeakerLive.Show do end @impl true - def handle_params(%{"id" => id}, _, socket) do - {:noreply, - socket - |> assign(:page_title, page_title(socket.assigns.live_action)) - |> assign(:speaker, Activities.get_speaker!(id))} + def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do + speaker = Activities.get_speaker!(id) + + if speaker.organization_id == organization_id do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:speaker, speaker)} + else + raise AtomicWeb.MismatchError + end end defp page_title(:show), do: "Show Speaker" diff --git a/lib/atomic_web/live/speaker_live/show.html.heex b/lib/atomic_web/live/speaker_live/show.html.heex index 73999d02c..b108acb76 100644 --- a/lib/atomic_web/live/speaker_live/show.html.heex +++ b/lib/atomic_web/live/speaker_live/show.html.heex @@ -1,8 +1,8 @@

Show Speaker

<%= if @live_action in [:edit] do %> - <.modal return_to={Routes.speaker_show_path(@socket, :show, @speaker)}> - <.live_component module={AtomicWeb.SpeakerLive.FormComponent} id={@speaker.id} title={@page_title} action={@live_action} speaker={@speaker} return_to={Routes.speaker_show_path(@socket, :show, @speaker)} /> + <.modal return_to={Routes.speaker_show_path(@socket, :show, @current_organization, @speaker)}> + <.live_component module={AtomicWeb.SpeakerLive.FormComponent} id={@speaker.id} organization={@current_organization} title={@page_title} action={@live_action} speaker={@speaker} return_to={Routes.speaker_show_path(@socket, :show, @current_organization, @speaker)} /> <% end %> @@ -18,5 +18,5 @@ -<%= live_patch("Edit", to: Routes.speaker_show_path(@socket, :edit, @speaker), class: "button") %> | -<%= live_redirect("Back", to: Routes.speaker_index_path(@socket, :index)) %> +<%= live_patch("Edit", to: Routes.speaker_show_path(@socket, :edit, @current_organization, @speaker), class: "button") %> | +<%= live_redirect("Back", to: Routes.speaker_index_path(@socket, :index, @current_organization)) %> diff --git a/lib/atomic_web/live/user_live/edit.html.heex b/lib/atomic_web/live/user_live/edit.html.heex index dffb93cee..4896ca8b5 100644 --- a/lib/atomic_web/live/user_live/edit.html.heex +++ b/lib/atomic_web/live/user_live/edit.html.heex @@ -1 +1 @@ -<.live_component module={AtomicWeb.UserLive.FormComponent} user={@user} id={@current_user.id} courses={@courses} title={@page_title} action={@live_action} return_to={Routes.activity_index_path(@socket, :index)} /> +<.live_component module={AtomicWeb.UserLive.FormComponent} user={@user} id={@current_user.id} courses={@courses} title={@page_title} action={@live_action} return_to={Routes.home_index_path(@conn, :index)} /> diff --git a/lib/atomic_web/plugs/authorize.ex b/lib/atomic_web/plugs/authorize.ex new file mode 100644 index 000000000..133332837 --- /dev/null +++ b/lib/atomic_web/plugs/authorize.ex @@ -0,0 +1,47 @@ +defmodule AtomicWeb.Plugs.Authorize do + @moduledoc false + import Plug.Conn + import Atomic.Organizations + + def init(opts), do: opts + + def call(conn, minimum_authorized_role) do + if authorized?(conn, minimum_authorized_role) do + conn + else + conn + |> send_resp(:not_found, "") + |> halt() + end + end + + defp authorized?(conn, minimum_authorized_role) do + organization_id = get_organization_id(conn) + + case {organization_id, conn.assigns.current_user} do + {nil, _} -> + false + + {id, user} -> + user_authorized?(user, id, minimum_authorized_role) + end + end + + defp get_organization_id(conn) do + case conn.params["organization_id"] do + organization_id when is_binary(organization_id) -> + organization_id + + _ -> + nil + end + end + + defp user_authorized?(user, organization_id, minimum_authorized_role) do + user_organizations = Enum.map(user.organizations, & &1.id) + role = get_role(user.id, organization_id) + allowed_roles = roles_bigger_than_or_equal(minimum_authorized_role) + + organization_id in user_organizations && role in allowed_roles + end +end diff --git a/lib/atomic_web/router.ex b/lib/atomic_web/router.ex index 5313a2eb0..7760747cc 100644 --- a/lib/atomic_web/router.ex +++ b/lib/atomic_web/router.ex @@ -17,64 +17,91 @@ defmodule AtomicWeb.Router do plug :accepts, ["json"] end + pipeline :admin do + plug AtomicWeb.Plugs.Authorize, :admin + end + + pipeline :member do + plug AtomicWeb.Plugs.Authorize, :member + end + + pipeline :follower do + plug AtomicWeb.Plugs.Authorize, :follower + end + scope "/", AtomicWeb do pipe_through :browser - live "/", ActivityLive.Index, :index + live_session :general, on_mount: [{AtomicWeb.Hooks, :general_user_state}] do + live "/", HomeLive.Index, :index + + live "/organizations", OrganizationLive.Index, :index + + scope "/organizations/:organization_id" do + live "/board/", BoardLive.Index, :index + live "/board/:id", BoardLive.Show, :show + end + end end scope "/", AtomicWeb do pipe_through [:browser, :require_authenticated_user] - live_session :logged_in, on_mount: [{AtomicWeb.Hooks, :current_user}] do + live_session :logged_in, on_mount: [{AtomicWeb.Hooks, :authenticated_user_state}] do live "/scanner", ScannerLive.Index, :index - live "/activities", ActivityLive.Index, :index - live "/activities/new", ActivityLive.New, :new - live "/activities/:id/edit", ActivityLive.Edit, :edit - live "/activities/:id", ActivityLive.Show, :show - - live "/departments/:org", DepartmentLive.Index, :index - live "/departments/:org/new", DepartmentLive.Index, :new - live "/departments/:org/:id/edit", DepartmentLive.Index, :edit - live "/departments/:org/:id", DepartmentLive.Show, :show - live "/departments/:org/:id/show/edit", DepartmentLive.Show, :edit - - live "/partners", PartnerLive.Index, :index - live "/partners/new", PartnerLive.Index, :new - live "/partners/:id/edit", PartnerLive.Index, :edit - live "/partners/:id", PartnerLive.Show, :show - live "/partners/:id/show/edit", PartnerLive.Show, :edit - - live "/speakers", SpeakerLive.Index, :index - live "/speakers/new", SpeakerLive.Index, :new - live "/speakers/:id/edit", SpeakerLive.Index, :edit - live "/speakers/:id", SpeakerLive.Show, :show - live "/speakers/:id/show/edit", SpeakerLive.Show, :edit + scope "/organizations/:organization_id" do + live "/", OrganizationLive.Show, :show - live "/organizations", OrganizationLive.Index, :index - live "/organizations/new", OrganizationLive.Index, :new - live "/organizations/:id/edit", OrganizationLive.Index, :edit - live "/organizations/:id", OrganizationLive.Show, :show - live "/organizations/:id/show/edit", OrganizationLive.Show, :edit + pipe_through :admin + live "/edit", OrganizationLive.Index, :edit + live "/show/edit", OrganizationLive.Show, :edit - live "/membership/:org", MembershipLive.Index, :index - live "/membership/:org/new", MembershipLive.New, :new - live "/membership/:org/:id", MembershipLive.Show, :show - live "/membership/:org/:id/edit", MembershipLive.Edit, :edit + live "/activities/new", ActivityLive.New, :new + live "/activities/:id/edit", ActivityLive.Edit, :edit - live "/card/:membership_id", CardLive.Show, :show + live "/departments/new", DepartmentLive.Index, :new + live "/departments/:id/edit", DepartmentLive.Index, :edit + live "/departments/:id/show/edit", DepartmentLive.Show, :edit + + live "/partners/new", PartnerLive.Index, :new + live "/partners/:id/edit", PartnerLive.Index, :edit + live "/partners/:id/show/edit", PartnerLive.Show, :edit + + live "/speakers/new", SpeakerLive.Index, :new + live "/speakers/:id/edit", SpeakerLive.Index, :edit + live "/speakers/:id/show/edit", SpeakerLive.Show, :edit + + live "/board/new", BoardLive.New, :new + live "/board/:id/edit", BoardLive.Edit, :edit + + live "/memberships", MembershipLive.Index, :index + live "/memberships/new", MembershipLive.New, :new + live "/memberships/:id", MembershipLive.Show, :show + live "/memberships/:id/edit", MembershipLive.Edit, :edit + end + + scope "/organizations/:organization_id" do + pipe_through :follower + live "/activities", ActivityLive.Index, :index + live "/activities/:id", ActivityLive.Show, :show - live "/board/:org", BoardLive.Index, :index - live "/board/:org/new", BoardLive.New, :new - live "/board/:org/:id", BoardLive.Show, :show - live "/board/:org/:id/edit", BoardLive.Edit, :edit - live "/memberships/:org", MembershipLive.Index, :index - live "/memberships/:org/new", MembershipLive.New, :new - live "/memberships/:org/:id", MembershipLive.Show, :show - live "/memberships/:org/:id/edit", MembershipLive.Edit, :edit + live "/departments", DepartmentLive.Index, :index + live "/departments/:id", DepartmentLive.Show, :show + + live "/partners", PartnerLive.Index, :index + live "/partners/:id", PartnerLive.Show, :show + + live "/speakers", SpeakerLive.Index, :index + live "/speakers/:id", SpeakerLive.Show, :show + end + + live "/organizations/new", OrganizationLive.Index, :new live "/user/edit", UserLive.Edit, :edit + + pipe_through :member + live "/card/:membership_id", CardLive.Show, :show end end diff --git a/lib/atomic_web/templates/layout/root.html.heex b/lib/atomic_web/templates/layout/root.html.heex index c03c73b66..494fe0a67 100644 --- a/lib/atomic_web/templates/layout/root.html.heex +++ b/lib/atomic_web/templates/layout/root.html.heex @@ -15,11 +15,19 @@
diff --git a/priv/repo/migrations/2022000000000_create_organizations.exs b/priv/repo/migrations/2022000000000_create_organizations.exs index 5f3d4de0e..43c8bf23e 100644 --- a/priv/repo/migrations/2022000000000_create_organizations.exs +++ b/priv/repo/migrations/2022000000000_create_organizations.exs @@ -6,6 +6,7 @@ defmodule Atomic.Repo.Migrations.CreateOrganizations do add :id, :binary_id, primary_key: true add :name, :string, null: false add :description, :text, null: false + add :organization_id, references(:organizations, type: :binary_id) add :location, :map timestamps() diff --git a/priv/repo/migrations/20221014155230_create_users_auth_tables.exs b/priv/repo/migrations/20221014155230_create_users_auth_tables.exs index 4e7eeb3f4..8b1353678 100644 --- a/priv/repo/migrations/20221014155230_create_users_auth_tables.exs +++ b/priv/repo/migrations/20221014155230_create_users_auth_tables.exs @@ -9,6 +9,10 @@ defmodule Atomic.Repo.Migrations.CreateUsersAuthTables do add :email, :citext, null: false add :hashed_password, :string, null: false add :confirmed_at, :naive_datetime + + add :default_organization_id, + references(:organizations, type: :binary_id, on_delete: :delete_all) + add :role, :string, null: false timestamps() end diff --git a/priv/repo/migrations/20221129000955_create_speakers.exs b/priv/repo/migrations/20221129000955_create_speakers.exs index 651bd875f..7a35092c8 100644 --- a/priv/repo/migrations/20221129000955_create_speakers.exs +++ b/priv/repo/migrations/20221129000955_create_speakers.exs @@ -7,6 +7,7 @@ defmodule Atomic.Repo.Migrations.CreateSpeakers do add :name, :string add :bio, :text + add :organization_id, references(:organizations, on_delete: :delete_all, type: :binary_id) timestamps() end end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 41a3d7fed..60f8ef497 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -7,11 +7,12 @@ defmodule Atomic.Repo.Seeds do def run do [ + "organizations.exs", "courses.exs", "accounts.exs", - "organizations.exs", "departments.exs", - "activities.exs" + "activities.exs", + "memberships.exs" ] |> Enum.each(fn file -> Code.require_file("#{@seeds_dir}/#{file}") diff --git a/priv/repo/seeds/accounts.exs b/priv/repo/seeds/accounts.exs index 58a9c04f1..544f0679c 100644 --- a/priv/repo/seeds/accounts.exs +++ b/priv/repo/seeds/accounts.exs @@ -2,6 +2,7 @@ defmodule Atomic.Repo.Seeds.Accounts do alias Atomic.Accounts alias Atomic.Accounts.{Course, User} alias Atomic.Repo + alias Atomic.Organizations.Organization def run do case Repo.all(User) do @@ -106,6 +107,7 @@ defmodule Atomic.Repo.Seeds.Accounts do def create_users(characters, role) do courses = Repo.all(Course) + organizations = Repo.all(Organization) for character <- characters do email = (character |> String.downcase() |> String.replace(~r/\s*/, "")) <> "@mail.pt" @@ -115,7 +117,8 @@ defmodule Atomic.Repo.Seeds.Accounts do "email" => email, "password" => "password1234", "role" => role, - "course_id" => Enum.random(courses).id + "course_id" => Enum.random(courses).id, + "default_organization_id" => Enum.random(organizations).id } case Accounts.register_user(user) do diff --git a/priv/repo/seeds/activities.exs b/priv/repo/seeds/activities.exs index 5316b8318..795dcf6b4 100644 --- a/priv/repo/seeds/activities.exs +++ b/priv/repo/seeds/activities.exs @@ -1,4 +1,5 @@ defmodule Atomic.Repo.Seeds.Activities do + alias Atomic.Activities.ActivityDepartment alias Atomic.Repo alias Atomic.Accounts.User @@ -8,6 +9,7 @@ defmodule Atomic.Repo.Seeds.Activities do def run do seed_activities() + seed_activities_departments() seed_enrollments() end @@ -36,10 +38,7 @@ defmodule Atomic.Repo.Seeds.Activities do location: location } ], - enrolled: 0, - departments: [ - Repo.get_by(Department, name: "Merchandise and Partnerships") |> Map.get(:id) - ] + enrolled: 0 } ) |> Repo.insert!() @@ -59,13 +58,7 @@ defmodule Atomic.Repo.Seeds.Activities do finish: DateTime.from_naive!(~N[2023-04-01 12:00:00], "Etc/UTC"), location: location } - ], - enrollments: [ - %{ - user_id: 1 - } - ], - departments: [Repo.get_by(Department, name: "Marketing and Content") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -85,13 +78,7 @@ defmodule Atomic.Repo.Seeds.Activities do finish: DateTime.from_naive!(~N[2023-04-01 12:00:00], "Etc/UTC"), location: location } - ], - enrollments: [ - %{ - user_id: 1 - } - ], - departments: [Repo.get_by(Department, name: "Recreative") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -111,13 +98,7 @@ defmodule Atomic.Repo.Seeds.Activities do finish: DateTime.from_naive!(~N[2023-04-01 12:00:00], "Etc/UTC"), location: location } - ], - enrollments: [ - %{ - user_id: 1 - } - ], - departments: [Repo.get_by(Department, name: "Pedagogical") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -136,13 +117,7 @@ defmodule Atomic.Repo.Seeds.Activities do finish: DateTime.from_naive!(~N[2023-04-01 12:00:00], "Etc/UTC"), location: location } - ], - enrollments: [ - %{ - user_id: 1 - } - ], - departments: [Repo.get_by(Department, name: "CAOS") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -162,10 +137,7 @@ defmodule Atomic.Repo.Seeds.Activities do location: location } ], - enrolled: 0, - departments: [ - Repo.get_by(Department, name: "Merchandise and Partnerships") |> Map.get(:id) - ] + enrolled: 0 } ) |> Repo.insert!() @@ -185,13 +157,7 @@ defmodule Atomic.Repo.Seeds.Activities do finish: DateTime.from_naive!(~N[2023-04-05 13:00:00], "Etc/UTC"), location: location } - ], - enrollments: [ - %{ - user_id: 1 - } - ], - departments: [Repo.get_by(Department, name: "Marketing and Content") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -211,13 +177,7 @@ defmodule Atomic.Repo.Seeds.Activities do finish: DateTime.from_naive!(~N[2023-04-06 17:00:00], "Etc/UTC"), location: location } - ], - enrollments: [ - %{ - user_id: 1 - } - ], - departments: [Repo.get_by(Department, name: "Recreative") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -227,6 +187,22 @@ defmodule Atomic.Repo.Seeds.Activities do end end + def seed_activities_departments() do + department = Repo.get_by(Department, name: "Merchandise and Partnerships") + + case Repo.all(ActivityDepartment) do + [] -> + for activity <- Repo.all(Activity) do + %ActivityDepartment{} + |> ActivityDepartment.changeset(%{ + activity_id: activity.id, + department_id: department.id + }) + |> Repo.insert!() + end + end + end + def seed_enrollments() do case Repo.all(Enrollment) do [] -> diff --git a/priv/repo/seeds/memberships.exs b/priv/repo/seeds/memberships.exs new file mode 100644 index 000000000..d6d16fd75 --- /dev/null +++ b/priv/repo/seeds/memberships.exs @@ -0,0 +1,69 @@ +defmodule Atomic.Repo.Seeds.Memberships do + alias Atomic.Organizations.Organization + alias Atomic.Organizations.Membership + alias Atomic.Accounts.User + alias Atomic.Organizations + alias Atomic.Repo + + def run do + seed_memberships() + seed_users_organizations() + end + + def seed_memberships() do + users = Repo.all(User) + organizations = Repo.all(Organization) + + for user <- users do + for organization <- organizations do + prob = 50 + random_number = :rand.uniform(100) + + if random_number < prob do + %Membership{} + |> Membership.changeset(%{ + "user_id" => user.id, + "organization_id" => organization.id, + "created_by_id" => Enum.random(users).id, + "role" => Enum.random([:follower, :member, :admin, :owner]) + }) + |> Repo.insert!() + end + end + end + end + + def seed_users_organizations() do + years = ["2019/2020", "2020/2021", "2021/2022", "2022/2023", "2023/2024"] + organizations = Repo.all(Organization) + users = Repo.all(User) + + titles = [ + "Presidente", + "Vice-Presidente", + "Tesoureiro", + "Secretário", + "Presidente da MAG", + "Secretário da MAG", + "Presidente do CF", + "Secretário do CF" + ] + + len_titles = length(titles) + + for year <- years do + for org <- organizations do + for {user, title} <- Enum.zip(Enum.take_random(users, len_titles), titles) do + Organizations.create_user_organization(%{ + "year" => year, + "organization_id" => org.id, + "user_id" => user.id, + "title" => title + }) + end + end + end + end +end + +Atomic.Repo.Seeds.Memberships.run() diff --git a/priv/repo/seeds/organizations.exs b/priv/repo/seeds/organizations.exs index eea6bcee9..88f9d5d5a 100644 --- a/priv/repo/seeds/organizations.exs +++ b/priv/repo/seeds/organizations.exs @@ -9,8 +9,6 @@ defmodule Atomic.Repo.Seeds.Organizations do def run do seed_organizations() - seed_users_organizations() - seed_memberships() end def seed_organizations() do @@ -102,61 +100,6 @@ defmodule Atomic.Repo.Seeds.Organizations do :ok end end - - def seed_memberships() do - users = Repo.all(User) - organizations = Repo.all(Organization) - - for user <- users do - for organization <- organizations do - prob = 50 - random_number = :rand.uniform(100) - - if random_number < prob do - %Membership{} - |> Membership.changeset(%{ - "user_id" => user.id, - "organization_id" => organization.id, - "created_by_id" => Enum.random(users).id, - "role" => Enum.random([:follower, :member, :admin, :owner]) - }) - |> Repo.insert!() - end - end - end - end - - def seed_users_organizations() do - years = ["2019/2020", "2020/2021", "2021/2022", "2022/2023", "2023/2024"] - organizations = Repo.all(Organization) - users = Repo.all(User) - - titles = [ - "Presidente", - "Vice-Presidente", - "Tesoureiro", - "Secretário", - "Presidente da MAG", - "Secretário da MAG", - "Presidente do CF", - "Secretário do CF" - ] - - len_titles = length(titles) - - for year <- years do - for org <- organizations do - for {user, title} <- Enum.zip(Enum.take_random(users, len_titles), titles) do - Organizations.create_user_organization(%{ - "year" => year, - "organization_id" => org.id, - "user_id" => user.id, - "title" => title - }) - end - end - end - end end Atomic.Repo.Seeds.Organizations.run() diff --git a/test/atomic/activities_test.exs b/test/atomic/activities_test.exs index dc3d22898..d4f20a8b0 100644 --- a/test/atomic/activities_test.exs +++ b/test/atomic/activities_test.exs @@ -4,6 +4,7 @@ defmodule Atomic.ActivitiesTest do alias Atomic.Activities import Atomic.Factory import Atomic.ActivitiesFixtures + alias Atomic.OrganizationsFixtures describe "activities" do alias Atomic.Activities.Activity @@ -178,7 +179,11 @@ defmodule Atomic.ActivitiesTest do end test "create_speaker/1 with valid data creates a speaker" do - valid_attrs = %{bio: "some bio", name: "some name"} + valid_attrs = %{ + bio: "some bio", + name: "some name", + organization_id: OrganizationsFixtures.organization_fixture().id + } assert {:ok, %Speaker{} = speaker} = Activities.create_speaker(valid_attrs) assert speaker.bio == "some bio" diff --git a/test/atomic/partnerships_test.exs b/test/atomic/partnerships_test.exs index 49f09b749..9066440e2 100644 --- a/test/atomic/partnerships_test.exs +++ b/test/atomic/partnerships_test.exs @@ -4,8 +4,8 @@ defmodule Atomic.PartnershipsTest do alias Atomic.Partnerships describe "partnerships" do + alias Atomic.OrganizationsFixtures alias Atomic.Partnerships.Partner - import Atomic.PartnershipsFixtures @invalid_attrs %{description: nil, name: nil} @@ -21,9 +21,14 @@ defmodule Atomic.PartnershipsTest do end test "create_partner/1 with valid data creates a partner" do - valid_attrs = %{description: "some description", name: "some name"} + valid_attrs = %{ + description: "some description", + name: "some name", + organization_id: OrganizationsFixtures.organization_fixture().id + } assert {:ok, %Partner{} = partner} = Partnerships.create_partner(valid_attrs) + assert partner.description == "some description" assert partner.name == "some name" end diff --git a/test/atomic_web/controllers/page_controller_test.exs b/test/atomic_web/controllers/page_controller_test.exs index ee513681a..e1a26847e 100644 --- a/test/atomic_web/controllers/page_controller_test.exs +++ b/test/atomic_web/controllers/page_controller_test.exs @@ -1,8 +1,9 @@ defmodule AtomicWeb.PageControllerTest do use AtomicWeb.ConnCase + setup [:register_and_log_in_user] test "GET /", %{conn: conn} do conn = get(conn, "/") - assert html_response(conn, 200) =~ "Listing Activities" + assert html_response(conn, 200) =~ "Home" end end diff --git a/test/atomic_web/controllers/user_registration_controller_test.exs b/test/atomic_web/controllers/user_registration_controller_test.exs index 1b520f7c9..e83058dcc 100644 --- a/test/atomic_web/controllers/user_registration_controller_test.exs +++ b/test/atomic_web/controllers/user_registration_controller_test.exs @@ -19,11 +19,14 @@ defmodule AtomicWeb.UserRegistrationControllerTest do describe "POST /users/register" do @tag :capture_log test "creates account and logs the user in", %{conn: conn} do + organization = insert(:organization) + user_attrs = %{ name: Faker.Person.name(), email: Faker.Internet.email(), role: "student", - password: "password1234" + password: "password1234", + default_organization_id: organization.id } conn = diff --git a/test/atomic_web/controllers/user_session_controller_test.exs b/test/atomic_web/controllers/user_session_controller_test.exs index d79e729ed..e9bc5dea7 100644 --- a/test/atomic_web/controllers/user_session_controller_test.exs +++ b/test/atomic_web/controllers/user_session_controller_test.exs @@ -4,7 +4,8 @@ defmodule AtomicWeb.UserSessionControllerTest do import Atomic.AccountsFixtures setup do - %{user: insert(:user)} + organization = insert(:organization) + %{user: insert(:user, default_organization_id: organization.id)} end describe "GET /users/log_in" do diff --git a/test/atomic_web/live/department_live_test.exs b/test/atomic_web/live/department_live_test.exs deleted file mode 100644 index bda3e7a91..000000000 --- a/test/atomic_web/live/department_live_test.exs +++ /dev/null @@ -1,3 +0,0 @@ -defmodule AtomicWeb.DepartmentLiveTest do - use AtomicWeb.ConnCase -end diff --git a/test/atomic_web/live/organization_live_test.exs b/test/atomic_web/live/organization_live_test.exs deleted file mode 100644 index 125a7d4ae..000000000 --- a/test/atomic_web/live/organization_live_test.exs +++ /dev/null @@ -1,117 +0,0 @@ -defmodule AtomicWeb.OrganizationLiveTest do - use AtomicWeb.ConnCase - - import Phoenix.LiveViewTest - import Atomic.OrganizationsFixtures - - @create_attrs %{description: "some description", name: "some name"} - @update_attrs %{description: "some updated description", name: "some updated name"} - @invalid_attrs %{description: nil, name: nil} - - defp create_organization(_) do - organization = organization_fixture() - %{organization: organization} - end - - describe "Index" do - setup [:create_organization] - setup [:register_and_log_in_user] - - test "lists all organizations", %{conn: conn, organization: organization} do - {:ok, _index_live, html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert html =~ "Listing Organizations" - assert html =~ organization.description - end - - test "saves new organization", %{conn: conn} do - {:ok, index_live, _html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert index_live |> element("a", "New Organization") |> render_click() =~ - "New Organization" - - assert_patch(index_live, Routes.organization_index_path(conn, :new)) - - assert index_live - |> form("#organization-form", organization: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#organization-form", organization: @create_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.organization_index_path(conn, :index)) - - assert html =~ "Organization created successfully" - assert html =~ "some description" - end - - test "updates organization in listing", %{conn: conn, organization: organization} do - {:ok, index_live, _html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert index_live |> element("#organization-#{organization.id} a", "Edit") |> render_click() =~ - "Edit Organization" - - assert_patch(index_live, Routes.organization_index_path(conn, :edit, organization)) - - assert index_live - |> form("#organization-form", organization: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#organization-form", organization: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.organization_index_path(conn, :index)) - - assert html =~ "Organization updated successfully" - assert html =~ "some updated description" - end - - test "deletes organization in listing", %{conn: conn, organization: organization} do - {:ok, index_live, _html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert index_live - |> element("#organization-#{organization.id} a", "Delete") - |> render_click() - - refute has_element?(index_live, "#organization-#{organization.id}") - end - end - - describe "Show" do - setup [:create_organization] - setup [:register_and_log_in_user] - - test "displays organization", %{conn: conn, organization: organization} do - {:ok, _show_live, html} = - live(conn, Routes.organization_show_path(conn, :show, organization)) - - assert html =~ "Show Organization" - assert html =~ organization.description - end - - test "updates organization within modal", %{conn: conn, organization: organization} do - {:ok, show_live, _html} = - live(conn, Routes.organization_show_path(conn, :show, organization)) - - assert show_live |> element("a", "Edit") |> render_click() =~ - "Edit Organization" - - assert_patch(show_live, Routes.organization_show_path(conn, :edit, organization)) - - assert show_live - |> form("#organization-form", organization: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - show_live - |> form("#organization-form", organization: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.organization_show_path(conn, :show, organization)) - - assert html =~ "Organization updated successfully" - assert html =~ "some updated description" - end - end -end diff --git a/test/atomic_web/live/partner_live_test.exs b/test/atomic_web/live/partner_live_test.exs deleted file mode 100644 index 342cec686..000000000 --- a/test/atomic_web/live/partner_live_test.exs +++ /dev/null @@ -1,109 +0,0 @@ -defmodule AtomicWeb.PartnerLiveTest do - use AtomicWeb.ConnCase - - import Phoenix.LiveViewTest - import Atomic.PartnershipsFixtures - - @create_attrs %{description: "some description", name: "some name"} - @update_attrs %{description: "some updated description", name: "some updated name"} - @invalid_attrs %{description: nil, name: nil} - - defp create_partner(_) do - partner = partner_fixture() - %{partner: partner} - end - - describe "Index" do - setup [:create_partner] - setup [:register_and_log_in_user] - - test "lists all partnerships", %{conn: conn, partner: partner} do - {:ok, _index_live, html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert html =~ "Listing Partnerships" - assert html =~ partner.description - end - - test "saves new partner", %{conn: conn} do - {:ok, index_live, _html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert index_live |> element("a", "New Partner") |> render_click() =~ - "New Partner" - - assert_patch(index_live, Routes.partner_index_path(conn, :new)) - - assert index_live - |> form("#partner-form", partner: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#partner-form", partner: @create_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.partner_index_path(conn, :index)) - - assert html =~ "some description" - end - - test "updates partner in listing", %{conn: conn, partner: partner} do - {:ok, index_live, _html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert index_live |> element("#partner-#{partner.id} a", "Edit") |> render_click() =~ - "Edit Partner" - - assert_patch(index_live, Routes.partner_index_path(conn, :edit, partner)) - - assert index_live - |> form("#partner-form", partner: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#partner-form", partner: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.partner_index_path(conn, :index)) - - assert html =~ "some updated description" - end - - test "deletes partner in listing", %{conn: conn, partner: partner} do - {:ok, index_live, _html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert index_live |> element("#partner-#{partner.id} a", "Delete") |> render_click() - refute has_element?(index_live, "#partner-#{partner.id}") - end - end - - describe "Show" do - setup [:create_partner] - setup [:register_and_log_in_user] - - test "displays partner", %{conn: conn, partner: partner} do - {:ok, _show_live, html} = live(conn, Routes.partner_show_path(conn, :show, partner)) - - assert html =~ "Show Partner" - assert html =~ partner.description - end - - test "updates partner within modal", %{conn: conn, partner: partner} do - {:ok, show_live, _html} = live(conn, Routes.partner_show_path(conn, :show, partner)) - - assert show_live |> element("a", "Edit") |> render_click() =~ - "Edit Partner" - - assert_patch(show_live, Routes.partner_show_path(conn, :edit, partner)) - - assert show_live - |> form("#partner-form", partner: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - show_live - |> form("#partner-form", partner: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.partner_show_path(conn, :show, partner)) - - assert html =~ "some updated description" - end - end -end diff --git a/test/atomic_web/live/speaker_live_test.exs b/test/atomic_web/live/speaker_live_test.exs deleted file mode 100644 index 46a5f6200..000000000 --- a/test/atomic_web/live/speaker_live_test.exs +++ /dev/null @@ -1,112 +0,0 @@ -defmodule AtomicWeb.SpeakerLiveTest do - use AtomicWeb.ConnCase - - import Phoenix.LiveViewTest - import Atomic.ActivitiesFixtures - - @create_attrs %{bio: "some bio", name: "some name"} - @update_attrs %{bio: "some updated bio", name: "some updated name"} - @invalid_attrs %{bio: nil, name: nil} - - defp create_speaker(_) do - speaker = speaker_fixture() - %{speaker: speaker} - end - - describe "Index" do - setup [:create_speaker] - setup [:register_and_log_in_user] - - test "lists all speakers", %{conn: conn, speaker: speaker} do - {:ok, _index_live, html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert html =~ "Listing Speakers" - assert html =~ speaker.bio - end - - test "saves new speaker", %{conn: conn} do - {:ok, index_live, _html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert index_live |> element("a", "New Speaker") |> render_click() =~ - "New Speaker" - - assert_patch(index_live, Routes.speaker_index_path(conn, :new)) - - assert index_live - |> form("#speaker-form", speaker: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#speaker-form", speaker: @create_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.speaker_index_path(conn, :index)) - - assert html =~ "Speaker created successfully" - assert html =~ "some bio" - end - - test "updates speaker in listing", %{conn: conn, speaker: speaker} do - {:ok, index_live, _html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert index_live |> element("#speaker-#{speaker.id} a", "Edit") |> render_click() =~ - "Edit Speaker" - - assert_patch(index_live, Routes.speaker_index_path(conn, :edit, speaker)) - - assert index_live - |> form("#speaker-form", speaker: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#speaker-form", speaker: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.speaker_index_path(conn, :index)) - - assert html =~ "Speaker updated successfully" - assert html =~ "some updated bio" - end - - test "deletes speaker in listing", %{conn: conn, speaker: speaker} do - {:ok, index_live, _html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert index_live |> element("#speaker-#{speaker.id} a", "Delete") |> render_click() - refute has_element?(index_live, "#speaker-#{speaker.id}") - end - end - - describe "Show" do - setup [:create_speaker] - setup [:register_and_log_in_user] - - test "displays speaker", %{conn: conn, speaker: speaker} do - {:ok, _show_live, html} = live(conn, Routes.speaker_show_path(conn, :show, speaker)) - - assert html =~ "Show Speaker" - assert html =~ speaker.bio - end - - test "updates speaker within modal", %{conn: conn, speaker: speaker} do - {:ok, show_live, _html} = live(conn, Routes.speaker_show_path(conn, :show, speaker)) - - assert show_live |> element("a", "Edit") |> render_click() =~ - "Edit Speaker" - - assert_patch(show_live, Routes.speaker_show_path(conn, :edit, speaker)) - - assert show_live - |> form("#speaker-form", speaker: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - show_live - |> form("#speaker-form", speaker: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.speaker_show_path(conn, :show, speaker)) - - assert html =~ "Speaker updated successfully" - assert html =~ "some updated bio" - end - end -end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 83c71d0dd..4b06d4e08 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -47,7 +47,8 @@ defmodule AtomicWeb.ConnCase do test context. """ def register_and_log_in_user(%{conn: conn}) do - user = insert(:user) + organization = insert(:organization) + user = insert(:user, %{default_organization_id: organization.id}) %{conn: log_in_user(conn, user), user: user} end diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex index 672b55cc9..1c23208c6 100644 --- a/test/support/fixtures/accounts_fixtures.ex +++ b/test/support/fixtures/accounts_fixtures.ex @@ -4,13 +4,15 @@ defmodule Atomic.AccountsFixtures do entities via the `Atomic.Accounts` context. """ + alias Atomic.OrganizationsFixtures def unique_user_email, do: "user#{System.unique_integer()}@example.com" def valid_user_password, do: "password1234" def valid_user_attributes(attrs \\ %{}) do Enum.into(attrs, %{ email: unique_user_email(), - password: valid_user_password() + password: valid_user_password(), + default_organization_id: OrganizationsFixtures.organization_fixture().id }) end diff --git a/test/support/fixtures/activities_fixtures.ex b/test/support/fixtures/activities_fixtures.ex index 07b548f16..bfe520fc9 100644 --- a/test/support/fixtures/activities_fixtures.ex +++ b/test/support/fixtures/activities_fixtures.ex @@ -4,6 +4,8 @@ defmodule Atomic.ActivitiesFixtures do entities via the `Atomic.Activities` context. """ + alias Atomic.OrganizationsFixtures + @doc """ Generate a activity. """ @@ -44,7 +46,8 @@ defmodule Atomic.ActivitiesFixtures do attrs |> Enum.into(%{ bio: "some bio", - name: "some name" + name: "some name", + organization_id: OrganizationsFixtures.organization_fixture().id }) |> Atomic.Activities.create_speaker() diff --git a/test/support/fixtures/partnerships_fixtures.ex b/test/support/fixtures/partnerships_fixtures.ex index b76159664..8bf2a45c1 100644 --- a/test/support/fixtures/partnerships_fixtures.ex +++ b/test/support/fixtures/partnerships_fixtures.ex @@ -4,6 +4,8 @@ defmodule Atomic.PartnershipsFixtures do entities via the `Atomic.Partnerships` context. """ + alias Atomic.OrganizationsFixtures + @doc """ Generate a partner. """ @@ -12,7 +14,8 @@ defmodule Atomic.PartnershipsFixtures do attrs |> Enum.into(%{ description: "some description", - name: "some name" + name: "some name", + organization_id: OrganizationsFixtures.organization_fixture().id }) |> Atomic.Partnerships.create_partner()