Files
Claper/lib/claper/accounts.ex

920 lines
21 KiB
Elixir
Raw Permalink Normal View History

defmodule Claper.Accounts do
@moduledoc """
The Accounts context.
"""
import Ecto.Query, warn: false
2024-08-11 11:16:34 +02:00
alias Claper.Accounts
alias Claper.Repo
2025-11-20 10:44:06 +01:00
alias Claper.Accounts.{User, UserToken, UserNotifier, Role}
2024-07-11 12:41:05 +02:00
@doc """
Creates a user.
## Examples
iex> create_user(%{field: value})
{:ok, %User{}}
iex> create_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_user(attrs) do
2025-11-20 10:44:06 +01:00
# Get user role if not explicitly set
attrs = maybe_set_default_role(attrs)
2024-07-11 12:41:05 +02:00
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert(returning: [:uuid])
end
@doc """
Gets a user by email.
## Examples
iex> get_user_by_email("foo@example.com")
%User{}
iex> get_user_by_email("unknown@example.com")
nil
"""
def get_user_by_email(email) when is_binary(email) do
2024-12-28 13:13:55 -05:00
User
|> where([u], is_nil(u.deleted_at))
|> Repo.get_by(email: email)
end
2024-07-11 12:41:05 +02:00
@doc """
Gets a user by email and creates a new user if the user does not exist.
## Examples
iex> get_user_by_email_or_create("foo@example.com")
%User{}
iex> get_user_by_email_or_create("unknown@example.com")
%User{}
"""
def get_user_by_email_or_create(email) when is_binary(email) do
case get_user_by_email(email) do
nil ->
2025-11-20 10:44:06 +01:00
attrs = %{
2024-07-11 12:41:05 +02:00
email: email,
confirmed_at: DateTime.utc_now(),
2024-08-11 11:16:34 +02:00
is_randomized_password: true,
2024-07-11 12:41:05 +02:00
password: :crypto.strong_rand_bytes(32)
2025-11-20 10:44:06 +01:00
}
# Set default role if not explicitly set
attrs = maybe_set_default_role(attrs)
create_user(attrs)
2024-07-11 12:41:05 +02:00
user ->
{:ok, user}
end
end
2025-11-20 10:44:06 +01:00
@doc """
Lists all users that are not deleted.
## Examples
iex> list_users()
[%User{}, ...]
"""
def list_users(preload \\ []) do
User
|> where([u], is_nil(u.deleted_at))
|> Repo.all()
|> Repo.preload(preload)
end
2022-08-22 23:40:38 +02:00
@doc """
Gets a user by email and password.
## Examples
iex> get_user_by_email_and_password("foo@example.com", "correct_password")
%User{}
iex> get_user_by_email_and_password("foo@example.com", "invalid_password")
nil
"""
def get_user_by_email_and_password(email, password)
when is_binary(email) and is_binary(password) do
2024-12-28 13:13:55 -05:00
user = User |> where([u], u.email == ^email and is_nil(u.deleted_at)) |> Repo.one()
if user && User.valid_password?(user, password), do: user
2022-08-22 23:40:38 +02:00
end
@doc """
Gets a single user.
Raises `Ecto.NoResultsError` if the User does not exist.
## Examples
iex> get_user!(123)
%User{}
iex> get_user!(456)
** (Ecto.NoResultsError)
"""
def get_user!(id), do: Repo.get!(User, id)
2024-12-28 13:13:55 -05:00
def get_user(id) do
User
|> where([u], is_nil(u.deleted_at))
|> Repo.get(id)
end
@doc """
Soft deletes a user.
## Examples
iex> delete_user(user)
{:ok, %User{}}
iex> delete_user(user)
{:error, %Ecto.Changeset{}}
"""
def delete_user(%User{} = user) do
user
|> User.delete_changeset()
|> Repo.update()
end
@doc """
Returns true if the user has been soft deleted.
"""
def deleted?(%User{} = user) do
not is_nil(user.deleted_at)
end
## User registration
@doc """
Registers a user.
## Examples
iex> register_user(%{field: value})
{:ok, %User{}}
iex> register_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def register_user(attrs) do
2025-11-20 10:44:06 +01:00
# Get user role if not explicitly set
attrs = maybe_set_default_role(attrs)
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert(returning: [:uuid])
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
## Examples
iex> change_user_registration(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_registration(%User{} = user, attrs \\ %{}) do
User.registration_changeset(user, attrs, hash_password: false)
end
2025-11-20 10:44:06 +01:00
@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes for admin operations.
## Examples
iex> change_user(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user(%User{} = user, attrs \\ %{}) do
User.admin_changeset(user, attrs, hash_password: false)
end
@doc """
Updates a user for admin operations.
## Examples
iex> update_user(user, %{field: new_value})
{:ok, %User{}}
iex> update_user(user, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_user(%User{} = user, attrs) do
user
|> User.admin_changeset(attrs)
|> Repo.update()
end
## Settings
@doc """
Returns an `%Ecto.Changeset{}` for changing the user email.
## Examples
iex> change_user_email(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_email(user, attrs \\ %{}) do
User.email_changeset(user, attrs)
end
@doc """
Returns an `%Ecto.Changeset{}` for changing the user preferences.
## Examples
iex> change_user_preferences(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_preferences(user, attrs \\ %{}) do
User.preferences_changeset(user, attrs)
end
@doc """
Emulates that the email will change without actually changing
it in the database.
## Examples
iex> apply_user_email(user, "valid password", %{email: ...})
{:ok, %User{}}
iex> apply_user_email(user, "invalid password", %{email: ...})
{:error, %Ecto.Changeset{}}
"""
def apply_user_email(user, attrs) do
user
|> User.email_changeset(attrs)
|> Ecto.Changeset.apply_action(:update)
end
@doc """
Updates the user email using the given token.
If the token matches, the user email is updated and the token is deleted.
The confirmed_at date is also updated to the current time.
"""
def update_user_email(user, token) do
context = "change:#{user.email}"
with {:ok, query} <- UserToken.verify_change_email_token_query(token, context),
%UserToken{sent_to: email} <- Repo.one(query),
{:ok, _} <- Repo.transaction(user_email_multi(user, email, context)) do
:ok
else
_ -> :error
end
end
defp user_email_multi(user, email, context) do
changeset = user |> User.email_changeset(%{email: email}) |> User.confirm_changeset()
Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
end
2022-08-22 23:40:38 +02:00
@doc """
Updates the user password.
## Examples
iex> update_user_password(user, "valid password", %{password: ...})
{:ok, %User{}}
iex> update_user_password(user, "invalid password", %{password: ...})
{:error, %Ecto.Changeset{}}
"""
def update_user_password(user, password, attrs) do
changeset =
user
|> User.password_changeset(attrs)
|> User.validate_current_password(password)
Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|> Repo.transaction()
|> case do
{:ok, %{user: user}} -> {:ok, user}
{:error, :user, changeset, _} -> {:error, changeset}
end
end
2025-11-20 10:44:06 +01:00
# Alternative version with different signature - keeping for compatibility
def update_user_password(user, %{"current_password" => curr_pw} = attrs) do
changeset =
user
|> User.password_changeset(attrs)
|> User.validate_current_password(curr_pw)
Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|> Repo.transaction()
|> case do
{:ok, %{user: user}} -> {:ok, user}
{:error, :user, changeset, _} -> {:error, changeset}
end
end
2024-08-11 11:16:34 +02:00
@doc """
Sets the user password.
## Examples
iex> set_user_password(user, %{password: ...})
{:ok, %User{}}
iex> set_user_password(user, %{password: ...})
{:error, %Ecto.Changeset{}}
"""
def set_user_password(user, attrs) do
user
|> User.password_changeset(attrs |> Map.put("is_randomized_password", false))
|> Repo.update()
|> case do
{:ok, user} -> {:ok, user}
{:error, changeset} -> {:error, changeset}
end
end
2024-04-07 12:32:31 +02:00
@doc """
Updates the user preferences.
## Examples
iex> update_user_preferences(user, %{locale: "en})
{:ok, %User{}}
iex> update_user_preferences(user, %{locale: "invalid})
{:error, %Ecto.Changeset{}}
"""
def update_user_preferences(user, attrs \\ %{}) do
user
|> User.preferences_changeset(attrs)
|> Repo.update()
end
@doc """
2022-07-28 17:50:57 +02:00
Delivers the magic link email to the given user.
## Examples
2024-04-06 11:48:47 +02:00
iex> deliver_magic_link(user, &url(~p"/users/magic/&1"))
{:ok, %{to: ..., body: ...}}
"""
def deliver_magic_link(email, magic_link_url_fun)
when is_function(magic_link_url_fun, 1) do
{encoded_token, user_token} = UserToken.build_magic_token(email, "magic")
Repo.insert!(user_token)
UserNotifier.deliver_magic_link(email, magic_link_url_fun.(encoded_token))
Add quizz feature + improvements commit 705ea00064e552f482bff52c3c5b11d23fbd5b4c Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 21 10:08:42 2024 -0500 Change version commit 330173bd64bb18c5ea7e68a2122f66497981c3c3 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 21 10:01:52 2024 -0500 Fix layout commit 3cc075962e961f8a78b0c30eca9b79db6b9a5731 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Dec 19 14:20:59 2024 +0100 Update changelog commit 63b1fa7ee591d40e44005b7939f51c99cf3e119e Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 15 18:56:01 2024 +0100 Fix upper commit 8e7bb1990c58e343de5aa18036eb0916573fb4c6 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 15 18:49:20 2024 +0100 Add pagination for events commit 28beacd120f0a1081e670e4a06bbc185cc699beb Author: Alex Lion <dev@alexandrelion.com> Date: Mon Dec 9 21:03:13 2024 +0100 Add pagination commit c79d6cce947869b98795b9baf541a32952624969 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 8 21:24:23 2024 +0100 Fix tests commit caad25ad75b5937ca0906dca89dedaa4d58ae072 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 8 17:43:55 2024 +0100 Fix user registration bug commit 38c3eecc49d1397a8bb7a4a11203775396d00272 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 22:23:24 2024 +0100 Update changelog commit e648ef08a0f61cf4b554fcbf0a83e02a2249de0d Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 22:06:27 2024 +0100 Add obin commit 6925117818e117dbd60efea5ae6c81a26a57f76f Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 19:39:03 2024 +0100 WIP commit be9b2886d3b879452f5bae08b3cdd181cac254f8 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 16:19:09 2024 +0100 Add LTI AGS for quizzes commit 29a7c96de6d4e38b26dfaa61bfa5e689a16d4935 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Dec 5 13:30:49 2024 +0100 Add translations commit 249fdc9188c7613a6adafb0b983303c1ae7601bd Author: Alex Lion <dev@alexandrelion.com> Date: Tue Dec 3 21:37:27 2024 +0100 Add qti export commit c2d56e30cdb6c629e957c64e4393dfd9d5af7159 Author: Alex Lion <dev@alexandrelion.com> Date: Tue Dec 3 20:44:58 2024 +0100 Fix report embed commit a34c239f9014e53b079106f1935bc2b079d01eed Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 11:32:32 2024 +0100 Add export quiz commit 8d1f34b90635776ae40849bd75fd135693b116fb Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 00:56:50 2024 +0100 Improve design commit d9a7370419ed9e288eccf263c2715330831e45e4 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 00:43:27 2024 +0100 Add exports commit b374b7bbccfa655dfad7695d7a24c5ddd4a07b66 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Nov 28 15:22:41 2024 +0100 Remove presence on manager commit 404e759ae3d2f5e555ae20437204553bffdc5065 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Nov 28 15:22:33 2024 +0100 Improve engagement report commit 39dbec6692c2d3f74a97647a703993d6152bfa06 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 23 15:39:59 2024 +0100 Add translation commit 354c2e30aece5bc7d800893ce8dee3868a1c1f71 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 23 15:18:53 2024 +0100 Change product tour behavior commit 5f253812282fb11011694b8828580d886f1f5899 Author: Alex <dev@alexandrelion.com> Date: Tue Nov 19 19:59:32 2024 +0100 WIP commit f411180433a05b89fc9d029e2b313968985e5c3f Author: Alex <dev@alexandrelion.com> Date: Tue Nov 19 19:32:30 2024 +0100 WIP commit 2b5989774eeb839f7b7b2a49377aca9fe4d68c09 Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 19:31:27 2024 +0100 WIP commit c8750a667f131b68818859796670c3022c6d53fe Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 18:23:01 2024 +0100 WIP commit fdb9efecb5688423ed2c82cf445868040653d380 Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 17:55:57 2024 +0100 WIP commit 5d12b12ce33eb5c1ba2a3307ef4ac679b279f511 Author: Alex <dev@alexandrelion.com> Date: Sat Nov 16 21:22:12 2024 +0100 WIP commit 548b714fda61464517247910af7e3e1c2bdae8cf Author: Alex <dev@alexandrelion.com> Date: Fri Nov 15 15:34:00 2024 +0100 WIP commit f0c87f34ea2ac837b4b3b3d6fd51c32bd625371e Author: Alex <dev@alexandrelion.com> Date: Wed Nov 13 22:09:24 2024 +0100 WIP commit c0c8bf99a538653208e28300566cced3d444a764 Author: Alex <dev@alexandrelion.com> Date: Mon Nov 11 13:02:36 2024 +0100 WIP commit 245ea9b836c2e69c7269fc7d8c7fd2edd0032eed Author: Alex <dev@alexandrelion.com> Date: Sun Nov 10 19:07:36 2024 +0100 Add presenter commit 0cf50918d62a9ab5ea127698219e05f781c659bb Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 23:20:35 2024 +0100 Refactor reactions commit ef8ffefe56d5b19dd895be181437c461134176ab Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 22:21:13 2024 +0100 Add tests commit c4055142ed63d8ea1be921f527bcaf595a2b9268 Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 11:28:00 2024 +0100 WIP commit 779e6970f7ee7ca89aab2bdfcff6197895b9ce5e Author: Alex <dev@alexandrelion.com> Date: Fri Nov 8 17:21:11 2024 +0100 WIP commit 9d25c440b830ded7e6fc2e0bcc9353520ec4a951 Author: Alex <dev@alexandrelion.com> Date: Fri Nov 8 11:54:31 2024 +0100 WIP commit c0157487a9e20b6773e517553681915c12367851 Author: Alex <dev@alexandrelion.com> Date: Fri Nov 1 17:13:04 2024 +0100 Fix condition commit a64439fbf2d852e127deb00a11906fb86b0c9ece Author: Alex <dev@alexandrelion.com> Date: Fri Nov 1 12:16:21 2024 +0100 WIP commit a994d959afe20ee380d42feb5ca6da2ab832d569 Author: Alex <dev@alexandrelion.com> Date: Wed Oct 30 23:06:24 2024 +0100 Fix changeset commit 5b2935fc33577af21ccc2558b49d9a813f4835f3 Merge: cec1a97 7476269 Author: Alex <dev@alexandrelion.com> Date: Sun Oct 20 11:26:51 2024 +0200 Merge branch 'dev' into feature/quizz commit cec1a97650867da3a09d8e23d0756a3a573e1bc8 Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 22:52:00 2024 +0200 WIP commit f65854f638393ce80fd9d17642e8a90ee5c1a06e Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 16:55:42 2024 +0200 WIP commit 1e6429a386c56be6a8fdd2f083e273b50a6bc4c9 Merge: 1977959 6f8a2fd Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 13:49:22 2024 +0200 Merge branch 'dev' into feature/quizz # Conflicts: # lib/claper_web/live/event_live/manage.html.heex commit 1977959efb12b5fb2f1795a8547e501c02e62122 Author: Alex <dev@alexandrelion.com> Date: Sat Oct 5 12:57:09 2024 +0200 WIP
2024-12-21 10:09:29 -05:00
{:ok, encoded_token}
end
2022-07-28 17:50:57 +02:00
@doc """
Delivers the update email instructions to the given user.
## Examples
iex> deliver_update_email_instructions(user, current_email, &Routes.user_update_email_url(conn, :edit, &1))
{:ok, %{to: ..., body: ...}}
"""
def deliver_update_email_instructions(%User{} = user, current_email, update_email_url_fun)
when is_function(update_email_url_fun, 1) do
{encoded_token, user_token} = UserToken.build_email_token(user, "change:#{current_email}")
Repo.insert!(user_token)
UserNotifier.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token))
Add quizz feature + improvements commit 705ea00064e552f482bff52c3c5b11d23fbd5b4c Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 21 10:08:42 2024 -0500 Change version commit 330173bd64bb18c5ea7e68a2122f66497981c3c3 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 21 10:01:52 2024 -0500 Fix layout commit 3cc075962e961f8a78b0c30eca9b79db6b9a5731 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Dec 19 14:20:59 2024 +0100 Update changelog commit 63b1fa7ee591d40e44005b7939f51c99cf3e119e Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 15 18:56:01 2024 +0100 Fix upper commit 8e7bb1990c58e343de5aa18036eb0916573fb4c6 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 15 18:49:20 2024 +0100 Add pagination for events commit 28beacd120f0a1081e670e4a06bbc185cc699beb Author: Alex Lion <dev@alexandrelion.com> Date: Mon Dec 9 21:03:13 2024 +0100 Add pagination commit c79d6cce947869b98795b9baf541a32952624969 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 8 21:24:23 2024 +0100 Fix tests commit caad25ad75b5937ca0906dca89dedaa4d58ae072 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 8 17:43:55 2024 +0100 Fix user registration bug commit 38c3eecc49d1397a8bb7a4a11203775396d00272 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 22:23:24 2024 +0100 Update changelog commit e648ef08a0f61cf4b554fcbf0a83e02a2249de0d Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 22:06:27 2024 +0100 Add obin commit 6925117818e117dbd60efea5ae6c81a26a57f76f Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 19:39:03 2024 +0100 WIP commit be9b2886d3b879452f5bae08b3cdd181cac254f8 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 16:19:09 2024 +0100 Add LTI AGS for quizzes commit 29a7c96de6d4e38b26dfaa61bfa5e689a16d4935 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Dec 5 13:30:49 2024 +0100 Add translations commit 249fdc9188c7613a6adafb0b983303c1ae7601bd Author: Alex Lion <dev@alexandrelion.com> Date: Tue Dec 3 21:37:27 2024 +0100 Add qti export commit c2d56e30cdb6c629e957c64e4393dfd9d5af7159 Author: Alex Lion <dev@alexandrelion.com> Date: Tue Dec 3 20:44:58 2024 +0100 Fix report embed commit a34c239f9014e53b079106f1935bc2b079d01eed Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 11:32:32 2024 +0100 Add export quiz commit 8d1f34b90635776ae40849bd75fd135693b116fb Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 00:56:50 2024 +0100 Improve design commit d9a7370419ed9e288eccf263c2715330831e45e4 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 00:43:27 2024 +0100 Add exports commit b374b7bbccfa655dfad7695d7a24c5ddd4a07b66 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Nov 28 15:22:41 2024 +0100 Remove presence on manager commit 404e759ae3d2f5e555ae20437204553bffdc5065 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Nov 28 15:22:33 2024 +0100 Improve engagement report commit 39dbec6692c2d3f74a97647a703993d6152bfa06 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 23 15:39:59 2024 +0100 Add translation commit 354c2e30aece5bc7d800893ce8dee3868a1c1f71 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 23 15:18:53 2024 +0100 Change product tour behavior commit 5f253812282fb11011694b8828580d886f1f5899 Author: Alex <dev@alexandrelion.com> Date: Tue Nov 19 19:59:32 2024 +0100 WIP commit f411180433a05b89fc9d029e2b313968985e5c3f Author: Alex <dev@alexandrelion.com> Date: Tue Nov 19 19:32:30 2024 +0100 WIP commit 2b5989774eeb839f7b7b2a49377aca9fe4d68c09 Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 19:31:27 2024 +0100 WIP commit c8750a667f131b68818859796670c3022c6d53fe Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 18:23:01 2024 +0100 WIP commit fdb9efecb5688423ed2c82cf445868040653d380 Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 17:55:57 2024 +0100 WIP commit 5d12b12ce33eb5c1ba2a3307ef4ac679b279f511 Author: Alex <dev@alexandrelion.com> Date: Sat Nov 16 21:22:12 2024 +0100 WIP commit 548b714fda61464517247910af7e3e1c2bdae8cf Author: Alex <dev@alexandrelion.com> Date: Fri Nov 15 15:34:00 2024 +0100 WIP commit f0c87f34ea2ac837b4b3b3d6fd51c32bd625371e Author: Alex <dev@alexandrelion.com> Date: Wed Nov 13 22:09:24 2024 +0100 WIP commit c0c8bf99a538653208e28300566cced3d444a764 Author: Alex <dev@alexandrelion.com> Date: Mon Nov 11 13:02:36 2024 +0100 WIP commit 245ea9b836c2e69c7269fc7d8c7fd2edd0032eed Author: Alex <dev@alexandrelion.com> Date: Sun Nov 10 19:07:36 2024 +0100 Add presenter commit 0cf50918d62a9ab5ea127698219e05f781c659bb Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 23:20:35 2024 +0100 Refactor reactions commit ef8ffefe56d5b19dd895be181437c461134176ab Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 22:21:13 2024 +0100 Add tests commit c4055142ed63d8ea1be921f527bcaf595a2b9268 Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 11:28:00 2024 +0100 WIP commit 779e6970f7ee7ca89aab2bdfcff6197895b9ce5e Author: Alex <dev@alexandrelion.com> Date: Fri Nov 8 17:21:11 2024 +0100 WIP commit 9d25c440b830ded7e6fc2e0bcc9353520ec4a951 Author: Alex <dev@alexandrelion.com> Date: Fri Nov 8 11:54:31 2024 +0100 WIP commit c0157487a9e20b6773e517553681915c12367851 Author: Alex <dev@alexandrelion.com> Date: Fri Nov 1 17:13:04 2024 +0100 Fix condition commit a64439fbf2d852e127deb00a11906fb86b0c9ece Author: Alex <dev@alexandrelion.com> Date: Fri Nov 1 12:16:21 2024 +0100 WIP commit a994d959afe20ee380d42feb5ca6da2ab832d569 Author: Alex <dev@alexandrelion.com> Date: Wed Oct 30 23:06:24 2024 +0100 Fix changeset commit 5b2935fc33577af21ccc2558b49d9a813f4835f3 Merge: cec1a97 7476269 Author: Alex <dev@alexandrelion.com> Date: Sun Oct 20 11:26:51 2024 +0200 Merge branch 'dev' into feature/quizz commit cec1a97650867da3a09d8e23d0756a3a573e1bc8 Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 22:52:00 2024 +0200 WIP commit f65854f638393ce80fd9d17642e8a90ee5c1a06e Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 16:55:42 2024 +0200 WIP commit 1e6429a386c56be6a8fdd2f083e273b50a6bc4c9 Merge: 1977959 6f8a2fd Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 13:49:22 2024 +0200 Merge branch 'dev' into feature/quizz # Conflicts: # lib/claper_web/live/event_live/manage.html.heex commit 1977959efb12b5fb2f1795a8547e501c02e62122 Author: Alex <dev@alexandrelion.com> Date: Sat Oct 5 12:57:09 2024 +0200 WIP
2024-12-21 10:09:29 -05:00
{:ok, encoded_token}
end
2022-08-22 23:40:38 +02:00
@doc """
Returns an `%Ecto.Changeset{}` for changing the user password.
## Examples
iex> change_user_password(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_password(user, attrs \\ %{}) do
User.password_changeset(user, attrs)
end
## Session
@doc """
Generates a session token.
"""
def generate_user_session_token(user) do
{token, user_token} = UserToken.build_session_token(user)
Repo.insert!(user_token)
token
end
@doc """
Gets the user with the given signed token.
"""
def get_user_by_session_token(token) do
{:ok, query} = UserToken.verify_session_token_query(token)
Repo.one(query)
end
2022-07-28 17:50:57 +02:00
@doc """
Verify the token for a given user email is valid.
"""
def magic_token_valid?(email) do
query = UserToken.user_magic_and_contexts_expiry_query(email)
Repo.exists?(query)
end
@doc """
Deletes the signed token with the given context.
"""
def delete_session_token(token) do
Repo.delete_all(UserToken.token_and_context_query(token, "session"))
:ok
end
## Confirmation
@doc """
Delivers the confirmation email instructions to the given user.
## Examples
iex> deliver_user_confirmation_instructions(user, &Routes.user_confirmation_url(conn, :edit, &1))
{:ok, %{to: ..., body: ...}}
iex> deliver_user_confirmation_instructions(confirmed_user, &Routes.user_confirmation_url(conn, :edit, &1))
{:error, :already_confirmed}
"""
def deliver_user_confirmation_instructions(%User{} = user, confirmation_url_fun)
when is_function(confirmation_url_fun, 1) do
if user.confirmed_at do
{:error, :already_confirmed}
else
{encoded_token, user_token} = UserToken.build_email_token(user, "confirm")
Repo.insert!(user_token)
UserNotifier.deliver_confirmation_instructions(user, confirmation_url_fun.(encoded_token))
Add quizz feature + improvements commit 705ea00064e552f482bff52c3c5b11d23fbd5b4c Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 21 10:08:42 2024 -0500 Change version commit 330173bd64bb18c5ea7e68a2122f66497981c3c3 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 21 10:01:52 2024 -0500 Fix layout commit 3cc075962e961f8a78b0c30eca9b79db6b9a5731 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Dec 19 14:20:59 2024 +0100 Update changelog commit 63b1fa7ee591d40e44005b7939f51c99cf3e119e Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 15 18:56:01 2024 +0100 Fix upper commit 8e7bb1990c58e343de5aa18036eb0916573fb4c6 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 15 18:49:20 2024 +0100 Add pagination for events commit 28beacd120f0a1081e670e4a06bbc185cc699beb Author: Alex Lion <dev@alexandrelion.com> Date: Mon Dec 9 21:03:13 2024 +0100 Add pagination commit c79d6cce947869b98795b9baf541a32952624969 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 8 21:24:23 2024 +0100 Fix tests commit caad25ad75b5937ca0906dca89dedaa4d58ae072 Author: Alex Lion <dev@alexandrelion.com> Date: Sun Dec 8 17:43:55 2024 +0100 Fix user registration bug commit 38c3eecc49d1397a8bb7a4a11203775396d00272 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 22:23:24 2024 +0100 Update changelog commit e648ef08a0f61cf4b554fcbf0a83e02a2249de0d Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 22:06:27 2024 +0100 Add obin commit 6925117818e117dbd60efea5ae6c81a26a57f76f Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 19:39:03 2024 +0100 WIP commit be9b2886d3b879452f5bae08b3cdd181cac254f8 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Dec 7 16:19:09 2024 +0100 Add LTI AGS for quizzes commit 29a7c96de6d4e38b26dfaa61bfa5e689a16d4935 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Dec 5 13:30:49 2024 +0100 Add translations commit 249fdc9188c7613a6adafb0b983303c1ae7601bd Author: Alex Lion <dev@alexandrelion.com> Date: Tue Dec 3 21:37:27 2024 +0100 Add qti export commit c2d56e30cdb6c629e957c64e4393dfd9d5af7159 Author: Alex Lion <dev@alexandrelion.com> Date: Tue Dec 3 20:44:58 2024 +0100 Fix report embed commit a34c239f9014e53b079106f1935bc2b079d01eed Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 11:32:32 2024 +0100 Add export quiz commit 8d1f34b90635776ae40849bd75fd135693b116fb Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 00:56:50 2024 +0100 Improve design commit d9a7370419ed9e288eccf263c2715330831e45e4 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 30 00:43:27 2024 +0100 Add exports commit b374b7bbccfa655dfad7695d7a24c5ddd4a07b66 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Nov 28 15:22:41 2024 +0100 Remove presence on manager commit 404e759ae3d2f5e555ae20437204553bffdc5065 Author: Alex Lion <dev@alexandrelion.com> Date: Thu Nov 28 15:22:33 2024 +0100 Improve engagement report commit 39dbec6692c2d3f74a97647a703993d6152bfa06 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 23 15:39:59 2024 +0100 Add translation commit 354c2e30aece5bc7d800893ce8dee3868a1c1f71 Author: Alex Lion <dev@alexandrelion.com> Date: Sat Nov 23 15:18:53 2024 +0100 Change product tour behavior commit 5f253812282fb11011694b8828580d886f1f5899 Author: Alex <dev@alexandrelion.com> Date: Tue Nov 19 19:59:32 2024 +0100 WIP commit f411180433a05b89fc9d029e2b313968985e5c3f Author: Alex <dev@alexandrelion.com> Date: Tue Nov 19 19:32:30 2024 +0100 WIP commit 2b5989774eeb839f7b7b2a49377aca9fe4d68c09 Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 19:31:27 2024 +0100 WIP commit c8750a667f131b68818859796670c3022c6d53fe Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 18:23:01 2024 +0100 WIP commit fdb9efecb5688423ed2c82cf445868040653d380 Author: Alex <dev@alexandrelion.com> Date: Sun Nov 17 17:55:57 2024 +0100 WIP commit 5d12b12ce33eb5c1ba2a3307ef4ac679b279f511 Author: Alex <dev@alexandrelion.com> Date: Sat Nov 16 21:22:12 2024 +0100 WIP commit 548b714fda61464517247910af7e3e1c2bdae8cf Author: Alex <dev@alexandrelion.com> Date: Fri Nov 15 15:34:00 2024 +0100 WIP commit f0c87f34ea2ac837b4b3b3d6fd51c32bd625371e Author: Alex <dev@alexandrelion.com> Date: Wed Nov 13 22:09:24 2024 +0100 WIP commit c0c8bf99a538653208e28300566cced3d444a764 Author: Alex <dev@alexandrelion.com> Date: Mon Nov 11 13:02:36 2024 +0100 WIP commit 245ea9b836c2e69c7269fc7d8c7fd2edd0032eed Author: Alex <dev@alexandrelion.com> Date: Sun Nov 10 19:07:36 2024 +0100 Add presenter commit 0cf50918d62a9ab5ea127698219e05f781c659bb Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 23:20:35 2024 +0100 Refactor reactions commit ef8ffefe56d5b19dd895be181437c461134176ab Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 22:21:13 2024 +0100 Add tests commit c4055142ed63d8ea1be921f527bcaf595a2b9268 Author: Alex <dev@alexandrelion.com> Date: Sat Nov 9 11:28:00 2024 +0100 WIP commit 779e6970f7ee7ca89aab2bdfcff6197895b9ce5e Author: Alex <dev@alexandrelion.com> Date: Fri Nov 8 17:21:11 2024 +0100 WIP commit 9d25c440b830ded7e6fc2e0bcc9353520ec4a951 Author: Alex <dev@alexandrelion.com> Date: Fri Nov 8 11:54:31 2024 +0100 WIP commit c0157487a9e20b6773e517553681915c12367851 Author: Alex <dev@alexandrelion.com> Date: Fri Nov 1 17:13:04 2024 +0100 Fix condition commit a64439fbf2d852e127deb00a11906fb86b0c9ece Author: Alex <dev@alexandrelion.com> Date: Fri Nov 1 12:16:21 2024 +0100 WIP commit a994d959afe20ee380d42feb5ca6da2ab832d569 Author: Alex <dev@alexandrelion.com> Date: Wed Oct 30 23:06:24 2024 +0100 Fix changeset commit 5b2935fc33577af21ccc2558b49d9a813f4835f3 Merge: cec1a97 7476269 Author: Alex <dev@alexandrelion.com> Date: Sun Oct 20 11:26:51 2024 +0200 Merge branch 'dev' into feature/quizz commit cec1a97650867da3a09d8e23d0756a3a573e1bc8 Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 22:52:00 2024 +0200 WIP commit f65854f638393ce80fd9d17642e8a90ee5c1a06e Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 16:55:42 2024 +0200 WIP commit 1e6429a386c56be6a8fdd2f083e273b50a6bc4c9 Merge: 1977959 6f8a2fd Author: Alex <dev@alexandrelion.com> Date: Sat Oct 19 13:49:22 2024 +0200 Merge branch 'dev' into feature/quizz # Conflicts: # lib/claper_web/live/event_live/manage.html.heex commit 1977959efb12b5fb2f1795a8547e501c02e62122 Author: Alex <dev@alexandrelion.com> Date: Sat Oct 5 12:57:09 2024 +0200 WIP
2024-12-21 10:09:29 -05:00
{:ok, encoded_token}
end
end
2022-08-22 23:40:38 +02:00
## Reset password
@doc """
Delivers the reset password email to the given user.
## Examples
iex> deliver_user_reset_password_instructions(user, &Routes.user_reset_password_url(conn, :edit, &1))
{:ok, %{to: ..., body: ...}}
"""
def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun)
when is_function(reset_password_url_fun, 1) do
{encoded_token, user_token} = UserToken.build_email_token(user, "reset_password")
Repo.insert!(user_token)
2024-08-23 15:33:05 +02:00
UserNotifier.deliver_reset_password_instructions(
user,
reset_password_url_fun.(encoded_token)
)
2022-08-22 23:40:38 +02:00
end
@doc """
Gets the user by reset password token.
## Examples
iex> get_user_by_reset_password_token("validtoken")
%User{}
iex> get_user_by_reset_password_token("invalidtoken")
nil
"""
def get_user_by_reset_password_token(token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
%User{} = user <- Repo.one(query) do
user
else
_ -> nil
end
end
@doc """
Resets the user password.
## Examples
iex> reset_user_password(user, %{password: "new long password", password_confirmation: "new long password"})
{:ok, %User{}}
iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"})
{:error, %Ecto.Changeset{}}
"""
def reset_user_password(user, attrs) do
Ecto.Multi.new()
|> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|> Repo.transaction()
|> case do
{:ok, %{user: user}} -> {:ok, user}
{:error, :user, changeset, _} -> {:error, changeset}
end
end
@doc """
Confirms a user by the given token.
If the token matches, the user account is marked as confirmed
and the token is deleted.
"""
def confirm_user(token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"),
%User{} = user <- Repo.one(query),
{:ok, %{user: user}} <- Repo.transaction(confirm_user_multi(user)) do
{:ok, user}
else
_ -> :error
end
end
defp confirm_user_multi(user) do
Ecto.Multi.new()
|> Ecto.Multi.update(:user, User.confirm_changeset(user))
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"]))
end
def confirm_magic(token) do
with {:ok, query} <- UserToken.verify_magic_token_query(token, "magic"),
%UserToken{} = token <- Repo.one(query),
{:ok, %{user: user}} <- Repo.transaction(confirm_magic_multi(token)) do
{:ok, user}
else
_ -> :error
end
end
defp confirm_magic_multi(%UserToken{} = token) do
Ecto.Multi.new()
|> Ecto.Multi.run(:run, fn repo, _changes ->
user = repo.get_by(User, email: token.sent_to)
2022-11-17 13:37:34 +01:00
if is_nil(user) do
UserNotifier.deliver_welcome(token.sent_to)
end
2022-11-17 13:37:34 +01:00
{:ok, user || %User{email: token.sent_to}}
end)
|> Ecto.Multi.insert_or_update(:user, fn %{run: user} -> User.confirm_changeset(user) end)
|> Ecto.Multi.delete_all(
:tokens,
UserToken.user_magic_and_contexts_query(token.sent_to, ["magic"])
)
end
2024-04-06 11:48:47 +02:00
2024-08-11 11:16:34 +02:00
## OIDC
def create_oidc_user(attrs) do
%Accounts.Oidc.User{}
|> Accounts.Oidc.User.changeset(attrs)
|> Repo.insert()
end
def remove_oidc_user(claper_user, issuer) do
Repo.delete_all(
from u in Accounts.Oidc.User,
where: u.issuer == ^issuer and u.user_id == ^claper_user.id
)
end
def get_all_oidc_users_by_email(email) do
Repo.all(from u in Accounts.Oidc.User, where: u.email == ^email)
end
def get_oidc_user_by_sub(sub) do
Repo.get_by(Accounts.Oidc.User, sub: sub)
end
def get_or_create_user_with_oidc(
%{
sub: sub
} = attrs
) do
case get_oidc_user_by_sub(sub) do
nil -> create_new_user(attrs)
%Accounts.Oidc.User{} = user -> update_oidc_user(user, attrs)
end
end
defp create_new_user(attrs) do
with {:ok, claper_user} <- get_user_by_email_or_create(attrs.email),
updated_attrs <-
Map.merge(attrs, %{user_id: claper_user.id}),
{:ok, user} <- create_oidc_user(updated_attrs) do
{:ok, user |> Repo.preload(:user)}
else
_ -> {:error, %{reason: :invalid_user, msg: "Invalid Claper user"}}
end
end
defp update_oidc_user(user, attrs) do
user
|> Accounts.Oidc.User.changeset(attrs)
|> Repo.update()
|> case do
{:ok, user} -> {:ok, user |> Repo.preload(:user)}
{:error, changeset} -> {:error, changeset}
end
end
2025-11-20 10:44:06 +01:00
## Role Management
@doc """
Returns the list of roles.
## Examples
iex> list_roles()
[%Role{}, ...]
"""
def list_roles do
Repo.all(Role)
end
@doc """
Gets a single role.
Raises `Ecto.NoResultsError` if the Role does not exist.
## Examples
iex> get_role!(123)
%Role{}
iex> get_role!(456)
** (Ecto.NoResultsError)
"""
def get_role!(id), do: Repo.get!(Role, id)
@doc """
Gets a role by name.
Returns nil if the Role does not exist.
## Examples
iex> get_role_by_name("admin")
%Role{}
iex> get_role_by_name("nonexistent")
nil
"""
def get_role_by_name(name) when is_binary(name) do
Repo.get_by(Role, name: name)
end
@doc """
Creates a role.
## Examples
iex> create_role(%{field: value})
{:ok, %Role{}}
iex> create_role(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_role(attrs \\ %{}) do
%Role{}
|> Role.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a role.
## Examples
iex> update_role(role, %{field: new_value})
{:ok, %Role{}}
iex> update_role(role, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_role(%Role{} = role, attrs) do
role
|> Role.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a role.
## Examples
iex> delete_role(role)
{:ok, %Role{}}
iex> delete_role(role)
{:error, %Ecto.Changeset{}}
"""
def delete_role(%Role{} = role) do
Repo.delete(role)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking role changes.
## Examples
iex> change_role(role)
%Ecto.Changeset{data: %Role{}}
"""
def change_role(%Role{} = role, attrs \\ %{}) do
Role.changeset(role, attrs)
end
@doc """
Assigns a role to a user by role name or role struct.
## Examples
iex> assign_role(user, "admin")
{:ok, %User{}}
iex> assign_role(user, %Role{id: 1})
{:ok, %User{}}
iex> assign_role(user, "unknown")
{:error, :role_not_found}
"""
def assign_role(%User{} = user, %Role{} = role) do
user
|> Ecto.Changeset.change(%{role_id: role.id})
|> Repo.update()
end
def assign_role(%User{} = user, role_name) when is_binary(role_name) do
case get_role_by_name(role_name) do
nil ->
{:error, :role_not_found}
role ->
user
|> Ecto.Changeset.change(%{role_id: role.id})
|> Repo.update()
end
end
@doc """
Gets the role of a user.
## Examples
iex> get_user_role(user)
%Role{}
iex> get_user_role(user_without_role)
nil
"""
def get_user_role(%User{} = user) do
user = user |> Repo.preload(:role)
user.role
end
@doc """
Lists users by role name.
## Examples
iex> list_users_by_role("admin")
[%User{}, ...]
iex> list_users_by_role("unknown")
[]
"""
def list_users_by_role(role_name) when is_binary(role_name) do
case get_role_by_name(role_name) do
nil ->
[]
role ->
User
|> where([u], u.role_id == ^role.id and is_nil(u.deleted_at))
|> Repo.all()
end
end
@doc """
Checks if a user has a specific role.
## Examples
iex> user_has_role?(user, "admin")
true
iex> user_has_role?(user, "unknown")
false
"""
def user_has_role?(%User{} = user, role_name) when is_binary(role_name) do
case get_user_role(user) do
nil -> false
role -> role.name == role_name
end
end
@doc """
Promotes a user to admin role.
## Examples
iex> promote_to_admin(user)
{:ok, %User{}}
iex> promote_to_admin(already_admin_user)
{:ok, %User{}}
"""
def promote_to_admin(%User{} = user) do
assign_role(user, "admin")
end
@doc """
Demotes a user from admin role to regular user role.
## Examples
iex> demote_from_admin(admin_user)
{:ok, %User{}}
iex> demote_from_admin(already_user_user)
{:ok, %User{}}
"""
def demote_from_admin(%User{} = user) do
assign_role(user, "user")
end
# Private helper to set default role if not already set
defp maybe_set_default_role(attrs) do
# Only set default role if role_id is not explicitly provided
case Map.get(attrs, :role_id) || Map.get(attrs, "role_id") do
nil -> set_default_user_role(attrs)
_ -> attrs
end
end
defp set_default_user_role(attrs) do
case get_role_by_name("user") do
nil -> attrs
user_role -> put_role_id(attrs, user_role.id)
end
end
defp put_role_id(attrs, role_id) do
key = determine_role_key(attrs)
Map.put(attrs, key, role_id)
end
defp determine_role_key(attrs) do
if has_string_keys?(attrs) do
"role_id"
else
:role_id
end
end
defp has_string_keys?(attrs) do
is_map(attrs) and map_size(attrs) > 0 and
Enum.all?(Map.keys(attrs), &is_binary/1)
end
end