WIP: feature/119_user_as_member #124

Closed
moritz wants to merge 2 commits from feature/119_user_as_member into main
35 changed files with 1208 additions and 192 deletions
Showing only changes of commit 4e6f5a517a - Show all commits

View file

@ -21,4 +21,15 @@ defmodule Mv.Accounts do
resource Mv.Accounts.Token
end
@doc """
Register a new user with password using AshAuthentication's standard action.
This creates a user and the notifier will automatically create a member.
"""
def register_with_password(params) do
# Use AshAuthentication's standard register_with_password action
Mv.Accounts.User
|> Ash.Changeset.for_create(:register_with_password, params)
|> Ash.create(domain: __MODULE__)
end
end

View file

@ -5,7 +5,8 @@ defmodule Mv.Accounts.User do
use Ash.Resource,
domain: Mv.Accounts,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication]
extensions: [AshAuthentication],
notifiers: [Mv.Accounts.User.MemberCreationNotifier]
# authorizers: [Ash.Policy.Authorizer]
@ -64,11 +65,11 @@ defmodule Mv.Accounts.User do
defaults [:read, :create, :destroy, :update]
create :create_user do
accept [:email]
accept [:email, :member_id]
end
update :update_user do
accept [:email]
accept [:email, :member_id]
end
# Admin action for direct password changes in admin panel
@ -121,9 +122,16 @@ defmodule Mv.Accounts.User do
# Global validations - applied to all relevant actions
validations do
# Password strength policy: minimum 8 characters for all password-related actions
# Password strength policy: minimum 8 characters
# Note: register_with_password has built-in AshAuthentication validation, but admin_set_password doesn't
validate string_length(:password, min: 8) do
where action_is([:register_with_password, :admin_set_password])
# Only needed for admin actions, AshAuthentication handles register_with_password
where action_is([:admin_set_password])
end
# Email uniqueness for registration actions
validate attribute_does_not_equal(:email, nil) do
where action_is([:register_with_password, :register_with_rauthy])
end
end
@ -143,6 +151,7 @@ defmodule Mv.Accounts.User do
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, sensitive?: true, allow_nil?: true
attribute :oidc_id, :string, allow_nil?: true
attribute :admin?, :boolean, allow_nil?: false, default: false, public?: true
end
relationships do
@ -152,6 +161,7 @@ defmodule Mv.Accounts.User do
identities do
identity :unique_email, [:email]
identity :unique_oidc_id, [:oidc_id]
identity :unique_member_id, [:member_id]
end
# You can customize this if you wish, but this is a safe default that

View file

@ -0,0 +1,71 @@
defmodule Mv.Accounts.User.MemberCreationNotifier do
@moduledoc """
Notifier that automatically creates a member for newly registered users.
This runs after user creation/registration and ensures every user has an associated member.
It's designed to work with AshAuthentication without interfering with LiveView integration.
"""
use Ash.Notifier
require Logger
@impl Ash.Notifier
def notify(%Ash.Notifier.Notification{
action: %{name: action_name},
resource: Mv.Accounts.User,
data: user
})
when action_name in [:register_with_password, :register_with_rauthy, :create_user] do
# Only create member if user doesn't already have one
if should_create_member?(user) do
create_member_for_user(user)
end
:ok
end
@impl Ash.Notifier
def notify(_), do: :ok
defp should_create_member?(user) do
# Check if user has a member_id and if that member actually exists
case user.member_id do
nil ->
true
member_id ->
case Ash.get(Mv.Membership.Member, member_id, domain: Mv.Membership) do
{:ok, _member} -> false
{:error, _} -> true
end
end
end
defp create_member_for_user(user) do
member_params = %{
email: to_string(user.email),
first_name: "User",
last_name: "Generated"
}
case Mv.Membership.create_member(member_params) do
{:ok, member} ->
# Update user with member_id
case Ash.Changeset.for_update(user, :update_user, %{member_id: member.id})
|> Ash.update(domain: Mv.Accounts) do
{:ok, _updated_user} ->
Logger.info(
"Successfully created and assigned member #{member.id} to user #{user.id}"
)
{:error, error} ->
Logger.warning(
"Failed to assign member #{member.id} to user #{user.id}: #{inspect(error)}"
)
end
{:error, error} ->
Logger.warning("Failed to create member for user #{user.id}: #{inspect(error)}")
end
end
end

View file

@ -170,5 +170,6 @@ defmodule Mv.Membership.Member do
relationships do
has_many :properties, Mv.Membership.Property
has_one :user, Mv.Accounts.User, destination_attribute: :member_id
end
end

View file

@ -10,6 +10,7 @@ defmodule Mv.Membership do
resource Mv.Membership.Member do
define :create_member, action: :create_member
define :list_members, action: :read
define :get_member!, action: :read, get_by: [:id]
define :update_member, action: :update_member
define :destroy_member, action: :destroy
end

View file

@ -24,6 +24,7 @@ defmodule MvWeb.Layouts do
"""
attr :flash, :map, required: true, doc: "the map of flash messages"
attr :current_user, :map, default: nil, doc: "the current user"
attr :current_scope, :map,
default: nil,
@ -33,7 +34,7 @@ defmodule MvWeb.Layouts do
def app(assigns) do
~H"""
<.navbar />
<.navbar current_user={@current_user} />
<main class="px-4 py-20 sm:px-6 lg:px-16">
<div class="mx-auto max-full space-y-4">
{render_slot(@inner_block)}

View file

@ -5,6 +5,8 @@ defmodule MvWeb.Layouts.Navbar do
use Phoenix.Component
use Gettext, backend: MvWeb.Gettext
attr :current_user, :map, default: nil
def navbar(assigns) do
~H"""
<header class="navbar bg-base-100 shadow-sm">
@ -65,7 +67,7 @@ defmodule MvWeb.Layouts.Navbar do
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow"
>
<li>
<a>
<a :if={@current_user} href={"/users/#{@current_user.id}"}>
{gettext("Profil")}
</a>
</li>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.MemberLive.Form do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{@page_title}
<:subtitle>
@ -155,7 +155,7 @@ defmodule MvWeb.MemberLive.Form do
AshPhoenix.Form.for_update(
member,
:update_member,
api: Mv.Membership,
domain: Mv.Membership,
as: "member",
params: params,
forms: [auto?: true]
@ -172,7 +172,7 @@ defmodule MvWeb.MemberLive.Form do
AshPhoenix.Form.for_create(
Mv.Membership.Member,
:create_member,
api: Mv.Membership,
domain: Mv.Membership,
as: "member",
params: %{"properties" => socket.assigns[:initial_properties]},
forms: [auto?: true]

View file

@ -1,4 +1,4 @@
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{gettext("Members")}
<:actions>

View file

@ -5,7 +5,7 @@ defmodule MvWeb.MemberLive.Show do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{@member.first_name} {@member.last_name}
<:subtitle>{gettext("This is a member record from your database.")}</:subtitle>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.PropertyLive.Form do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{@page_title}
<:subtitle>{gettext("Use this form to manage property records in your database.")}</:subtitle>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.PropertyLive.Index do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Listing Properties
<:actions>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.PropertyLive.Show do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Property {@property.id}
<:subtitle>This is a property record from your database.</:subtitle>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.PropertyTypeLive.Form do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{@page_title}
<:subtitle>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.PropertyTypeLive.Index do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Listing Property types
<:actions>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.PropertyTypeLive.Show do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Property type {@property_type.id}
<:subtitle>This is a property_type record from your database.</:subtitle>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.UserLive.Form do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{@page_title}
<:subtitle>{gettext("Use this form to manage user records in your database.")}</:subtitle>
@ -13,6 +13,53 @@ defmodule MvWeb.UserLive.Form do
<.form for={@form} id="user-form" phx-change="validate" phx-submit="save">
<.input field={@form[:email]} label={gettext("Email")} required type="email" />
<!-- Member Assignment Section -->
<div class="mt-6 space-y-4">
<h3 class="text-lg font-medium">{gettext("Member Assignment")}</h3>
<label class="flex items-center space-x-2">
<input
type="radio"
name="member_assignment_mode"
value="create_new"
phx-click="set_member_mode"
phx-value-mode="create_new"
checked={@member_assignment_mode == "create_new"}
class="radio radio-sm"
/>
<span class="text-sm">
{gettext("Create new member automatically")}
</span>
</label>
<label class="flex items-center space-x-2">
<input
type="radio"
name="member_assignment_mode"
value="assign_existing"
phx-click="set_member_mode"
phx-value-mode="assign_existing"
checked={@member_assignment_mode == "assign_existing"}
class="radio radio-sm"
/>
<span class="text-sm">
{gettext("Assign to existing member")}
</span>
</label>
<%= if @member_assignment_mode == "assign_existing" do %>
<div class="ml-6 mt-2">
<.input
field={@form[:member_id]}
label={gettext("Select Member")}
type="select"
options={@available_members}
prompt={gettext("Choose a member...")}
/>
</div>
<% end %>
</div>
<!-- Password Section -->
<div class="mt-6">
<label class="flex items-center space-x-2">
@ -109,12 +156,25 @@ defmodule MvWeb.UserLive.Form do
action = if is_nil(user), do: gettext("New"), else: gettext("Edit")
page_title = action <> " " <> gettext("User")
# Load available members that have no user assigned
{:ok, available_members} = Mv.Membership.list_members()
available_members_with_user = Ash.load!(available_members, :user)
available_member_options =
available_members_with_user
|> Enum.filter(fn member -> is_nil(member.user) end)
|> Enum.map(fn member ->
{"#{member.first_name} #{member.last_name} (#{member.email})", member.id}
end)
{:ok,
socket
|> assign(:return_to, return_to(params["return_to"]))
|> assign(user: user)
|> assign(:page_title, page_title)
|> assign(:show_password_fields, false)
|> assign(:member_assignment_mode, "create_new")
|> assign(:available_members, available_member_options)
|> assign_form()}
end
@ -133,6 +193,15 @@ defmodule MvWeb.UserLive.Form do
{:noreply, socket}
end
def handle_event("set_member_mode", %{"mode" => mode}, socket) do
socket =
socket
|> assign(:member_assignment_mode, mode)
|> assign_form()
{:noreply, socket}
end
def handle_event("validate", %{"user" => user_params}, socket) do
{:noreply, assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, user_params))}
end
@ -161,14 +230,30 @@ defmodule MvWeb.UserLive.Form do
if user do
# For existing users, use admin password action if password fields are shown
action = if show_password_fields, do: :admin_set_password, else: :update_user
AshPhoenix.Form.for_update(user, action, domain: Mv.Accounts, as: "user")
AshPhoenix.Form.for_update(user, action,
as: "user",
actor: socket.assigns.current_user,
domain: Mv.Accounts
)
else
# For new users, use password registration if password fields are shown
action = if show_password_fields, do: :register_with_password, else: :create_user
# Only include member_id if assign_existing mode is selected AND not using password action
accept =
if socket.assigns.member_assignment_mode == "assign_existing" and
not show_password_fields do
[:email, :member_id]
else
[:email]
end
AshPhoenix.Form.for_create(Mv.Accounts.User, action,
as: "user",
actor: socket.assigns.current_user,
domain: Mv.Accounts,
as: "user"
accept: accept
)
end

View file

@ -4,7 +4,10 @@ defmodule MvWeb.UserLive.Index do
@impl true
def mount(_params, _session, socket) do
users = Ash.read!(Mv.Accounts.User, domain: Mv.Accounts)
users =
Ash.read!(Mv.Accounts.User, domain: Mv.Accounts)
|> Ash.load!(:member)
sorted = Enum.sort_by(users, & &1.email)
{:ok,

View file

@ -1,4 +1,4 @@
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{gettext("Listing Users")}
<:actions>
@ -49,6 +49,11 @@
>
{user.email}
</:col>
<:col :let={user} label={gettext("Member")}>
{if user.member,
do: "#{user.member.first_name} #{user.member.last_name}",
else: gettext("No member")}
</:col>
<:col :let={user} label={gettext("OIDC ID")}>{user.oidc_id}</:col>
<:action :let={user}>

View file

@ -4,7 +4,7 @@ defmodule MvWeb.UserLive.Show do
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{gettext("User")} {@user.email}
<:subtitle>{gettext("This is a user record from your database.")}</:subtitle>
@ -22,6 +22,14 @@ defmodule MvWeb.UserLive.Show do
<.list>
<:item title={gettext("ID")}>{@user.id}</:item>
<:item title={gettext("Email")}>{@user.email}</:item>
<:item title={gettext("Member")}>
<div :if={@user.member}>
<.link navigate={~p"/members/#{@user.member.id}"} class="link link-primary">
{@user.member.first_name} {@user.member.last_name} ({@user.member.email})
</.link>
</div>
<span :if={!@user.member}>{gettext("No member assigned")}</span>
</:item>
<:item title={gettext("OIDC ID")}>{@user.oidc_id || gettext("Not set")}</:item>
<:item title={gettext("Password Authentication")}>
{if @user.hashed_password, do: gettext("Enabled"), else: gettext("Not enabled")}
@ -33,9 +41,13 @@ defmodule MvWeb.UserLive.Show do
@impl true
def mount(%{"id" => id}, _session, socket) do
user =
Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts)
|> Ash.load!(:member)
{:ok,
socket
|> assign(:page_title, gettext("Show User"))
|> assign(:user, Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts))}
|> assign(:user, user)}
end
end

View file

@ -58,6 +58,7 @@
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
"plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"},

View file

@ -16,13 +16,13 @@ msgid "Actions"
msgstr "Aktionen"
#: lib/mv_web/live/member_live/index.html.heex:77
#: lib/mv_web/live/user_live/index.html.heex:69
#: lib/mv_web/live/user_live/index.html.heex:70
#, elixir-autogen, elixir-format
msgid "Are you sure?"
msgstr "Bist du sicher?"
#: lib/mv_web/components/layouts.ex:71
#: lib/mv_web/components/layouts.ex:83
#: lib/mv_web/components/layouts.ex:72
#: lib/mv_web/components/layouts.ex:84
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr "Verbindung wird wiederhergestellt"
@ -35,14 +35,14 @@ msgid "City"
msgstr "Stadt"
#: lib/mv_web/live/member_live/index.html.heex:79
#: lib/mv_web/live/user_live/index.html.heex:71
#: lib/mv_web/live/user_live/index.html.heex:72
#, elixir-autogen, elixir-format
msgid "Delete"
msgstr "Löschen"
#: lib/mv_web/live/member_live/index.html.heex:71
#: lib/mv_web/live/user_live/form.ex:109
#: lib/mv_web/live/user_live/index.html.heex:63
#: lib/mv_web/live/user_live/form.ex:156
#: lib/mv_web/live/user_live/index.html.heex:64
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr "Bearbeite"
@ -57,7 +57,7 @@ msgstr "Mitglied bearbeiten"
#: lib/mv_web/live/member_live/index.html.heex:58
#: lib/mv_web/live/member_live/show.ex:27
#: lib/mv_web/live/user_live/form.ex:14
#: lib/mv_web/live/user_live/index.html.heex:48
#: lib/mv_web/live/user_live/index.html.heex:44
#: lib/mv_web/live/user_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Email"
@ -88,17 +88,17 @@ msgid "New Member"
msgstr "Neues Mitglied"
#: lib/mv_web/live/member_live/index.html.heex:68
#: lib/mv_web/live/user_live/index.html.heex:60
#: lib/mv_web/live/user_live/index.html.heex:61
#, elixir-autogen, elixir-format
msgid "Show"
msgstr "Anzeigen"
#: lib/mv_web/components/layouts.ex:78
#: lib/mv_web/components/layouts.ex:79
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr "Etwas ist schiefgelaufen!"
#: lib/mv_web/components/layouts.ex:66
#: lib/mv_web/components/layouts.ex:67
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr "Keine Internetverbindung gefunden"
@ -167,7 +167,7 @@ msgstr "Mitglied speichern"
#: lib/mv_web/live/member_live/form.ex:49
#: lib/mv_web/live/property_live/form.ex:41
#: lib/mv_web/live/property_type_live/form.ex:29
#: lib/mv_web/live/user_live/form.ex:92
#: lib/mv_web/live/user_live/form.ex:139
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr "Speichern..."
@ -261,7 +261,7 @@ msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt"
#: lib/mv_web/live/member_live/form.ex:52
#: lib/mv_web/live/property_live/form.ex:44
#: lib/mv_web/live/property_type_live/form.ex:32
#: lib/mv_web/live/user_live/form.ex:95
#: lib/mv_web/live/user_live/form.ex:142
#, elixir-autogen, elixir-format
msgid "Cancel"
msgstr "Abbrechen"
@ -286,7 +286,7 @@ msgstr "Beschreibung"
msgid "Edit User"
msgstr "Benutzer bearbeiten"
#: lib/mv_web/live/user_live/show.ex:27
#: lib/mv_web/live/user_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Enabled"
msgstr "Aktiviert"
@ -301,29 +301,79 @@ msgstr "ID"
msgid "Immutable"
msgstr "Unveränderlich"
#: lib/mv_web/components/layouts/navbar.ex:73
#, elixir-autogen, elixir-format
#: lib/mv_web/components/layouts/navbar.ex:75
#, elixir-autogen
msgid "Logout"
msgstr "Abmelden"
#: lib/mv_web/live/user_live/index.ex:12
#: lib/mv_web/live/user_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "Listing Users"
msgstr "Benutzer auflisten"
#: lib/mv_web/live/property_live/form.ex:27
#, elixir-autogen, elixir-format
msgid "Member"
msgstr "Mitglied"
#: lib/mv_web/components/layouts/navbar.ex:14
#: lib/mv_web/components/layouts/navbar.ex:16
#: lib/mv_web/live/member_live/index.ex:12
#: lib/mv_web/live/member_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "Members"
msgstr "Mitglieder"
#: lib/mv_web/live/property_live/form.ex:27
#: lib/mv_web/live/user_live/index.html.heex:52
#: lib/mv_web/live/user_live/show.ex:25
#, elixir-autogen, elixir-format
msgid "Member"
msgstr "Mitglied"
#: lib/mv_web/live/user_live/index.html.heex:55
#, elixir-autogen
msgid "No member"
msgstr "Kein Mitglied"
#: lib/mv_web/live/user_live/show.ex:31
#, elixir-autogen
msgid "No member assigned"
msgstr "Kein Mitglied zugeordnet"
#, fuzzy
msgid "Profile"
msgstr "Profil"
msgid "Edit your member information"
msgstr "Bearbeite deine Mitgliedsdaten"
#, fuzzy
msgid "Save Profile"
msgstr "Profil"
msgid "Profile updated successfully"
msgstr "Profil erfolgreich aktualisiert"
msgid "No member profile found"
msgstr "Kein Mitgliederprofil gefunden"
#: lib/mv_web/live/user_live/form.ex:18
#, elixir-autogen, fuzzy
msgid "Member Assignment"
msgstr "Mitglieder"
msgid "Create new member automatically"
msgstr "Neues Mitglied automatisch erstellen"
msgid "Assign to existing member"
msgstr "Bestehendem Mitglied zuordnen"
#: lib/mv_web/live/user_live/form.ex:54
#, elixir-autogen, fuzzy
msgid "Select Member"
msgstr "Mitglied auswählen"
#: lib/mv_web/live/user_live/form.ex:57
#, elixir-autogen, fuzzy
msgid "Choose a member..."
msgstr "Mitglied auswählen"
#: lib/mv_web/live/user_live/index.ex:15
#: lib/mv_web/live/user_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "Listing Users"
msgstr "Benutzer auflisten"
#: lib/mv_web/live/member_live/index.html.heex:50
#: lib/mv_web/live/property_type_live/form.ex:16
#, elixir-autogen, elixir-format
@ -335,29 +385,29 @@ msgstr "Name"
msgid "New User"
msgstr "Neuer Benutzer"
#: lib/mv_web/live/user_live/show.ex:27
#: lib/mv_web/live/user_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Not enabled"
msgstr "Nicht aktiviert"
#: lib/mv_web/live/user_live/show.ex:25
#: lib/mv_web/live/user_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "Not set"
msgstr "Nicht gesetzt"
#: lib/mv_web/live/user_live/form.ex:75
#: lib/mv_web/live/user_live/form.ex:83
#: lib/mv_web/live/user_live/form.ex:122
#: lib/mv_web/live/user_live/form.ex:130
#, elixir-autogen, elixir-format
msgid "Note"
msgstr "Hinweis"
#: lib/mv_web/live/user_live/index.html.heex:56
#: lib/mv_web/live/user_live/show.ex:25
#: lib/mv_web/live/user_live/index.html.heex:57
#: lib/mv_web/live/user_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "OIDC ID"
msgstr "OIDC ID"
#: lib/mv_web/live/user_live/show.ex:26
#: lib/mv_web/live/user_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "Password Authentication"
msgstr "Passwort-Authentifizierung"
@ -367,7 +417,7 @@ msgstr "Passwort-Authentifizierung"
msgid "Please select a property type first"
msgstr "Bitte wählen Sie zuerst einen Eigenschaftstyp"
#: lib/mv_web/components/layouts/navbar.ex:69
#: lib/mv_web/components/layouts/navbar.ex:71
#, elixir-autogen, elixir-format
msgid "Profil"
msgstr "Profil"
@ -412,17 +462,17 @@ msgstr "Alle Mitglieder auswählen"
msgid "Select member"
msgstr "Mitglied auswählen"
#: lib/mv_web/components/layouts/navbar.ex:72
#: lib/mv_web/components/layouts/navbar.ex:74
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr "Einstellungen"
#: lib/mv_web/live/user_live/form.ex:93
#: lib/mv_web/live/user_live/form.ex:140
#, elixir-autogen, elixir-format
msgid "Save User"
msgstr "Benutzer speichern"
#: lib/mv_web/live/user_live/show.ex:38
#: lib/mv_web/live/user_live/show.ex:50
#, elixir-autogen, elixir-format
msgid "Show User"
msgstr "Benutzer anzeigen"
@ -452,7 +502,7 @@ msgstr "Dieses Formular dient zur Verwaltung von Mitgliedern und deren Eigenscha
msgid "Use this form to manage user records in your database."
msgstr "Verwenden Sie dieses Formular, um Benutzer-Datensätze zu verwalten."
#: lib/mv_web/live/user_live/form.ex:110
#: lib/mv_web/live/user_live/form.ex:157
#: lib/mv_web/live/user_live/show.ex:9
#, elixir-autogen, elixir-format
msgid "User"
@ -478,77 +528,77 @@ msgstr "aufsteigend"
msgid "descending"
msgstr "absteigend"
#: lib/mv_web/live/user_live/form.ex:109
#: lib/mv_web/live/user_live/form.ex:156
#, elixir-autogen, elixir-format
msgid "New"
msgstr "Neuer"
#: lib/mv_web/live/user_live/form.ex:64
#: lib/mv_web/live/user_live/form.ex:111
#, elixir-autogen, elixir-format
msgid "Admin Note"
msgstr "Administrator-Hinweis"
#: lib/mv_web/live/user_live/form.ex:64
#: lib/mv_web/live/user_live/form.ex:111
#, elixir-autogen, elixir-format
msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
msgstr "Als Administrator können Sie direkt ein neues Passwort für diesen Benutzer setzen, wobei das gleiche sichere Ash Authentication System verwendet wird."
#: lib/mv_web/live/user_live/form.ex:55
#: lib/mv_web/live/user_live/form.ex:102
#, elixir-autogen, elixir-format
msgid "At least 8 characters"
msgstr "Mindestens 8 Zeichen"
#: lib/mv_web/live/user_live/form.ex:27
#: lib/mv_web/live/user_live/form.ex:74
#, elixir-autogen, elixir-format
msgid "Change Password"
msgstr "Passwort ändern"
#: lib/mv_web/live/user_live/form.ex:75
#: lib/mv_web/live/user_live/form.ex:122
#, elixir-autogen, elixir-format
msgid "Check 'Change Password' above to set a new password for this user."
msgstr "Aktivieren Sie 'Passwort ändern' oben, um ein neues Passwort für diesen Benutzer zu setzen."
#: lib/mv_web/live/user_live/form.ex:45
#: lib/mv_web/live/user_live/form.ex:92
#, elixir-autogen, elixir-format
msgid "Confirm Password"
msgstr "Passwort bestätigen"
#: lib/mv_web/live/user_live/form.ex:57
#: lib/mv_web/live/user_live/form.ex:104
#, elixir-autogen, elixir-format
msgid "Consider using special characters"
msgstr "Sonderzeichen empfohlen"
#: lib/mv_web/live/user_live/form.ex:56
#: lib/mv_web/live/user_live/form.ex:103
#, elixir-autogen, elixir-format
msgid "Include both letters and numbers"
msgstr "Buchstaben und Zahlen verwenden"
#: lib/mv_web/live/user_live/form.ex:35
#: lib/mv_web/live/user_live/form.ex:82
#, elixir-autogen, elixir-format
msgid "Password"
msgstr "Passwort"
#: lib/mv_web/live/user_live/form.ex:53
#: lib/mv_web/live/user_live/form.ex:100
#, elixir-autogen, elixir-format
msgid "Password requirements"
msgstr "Passwort-Anforderungen"
#: lib/mv_web/live/user_live/index.html.heex:25
#: lib/mv_web/live/user_live/index.html.heex:21
#, elixir-autogen, elixir-format
msgid "Select all users"
msgstr "Alle Benutzer auswählen"
#: lib/mv_web/live/user_live/index.html.heex:39
#: lib/mv_web/live/user_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Select user"
msgstr "Benutzer auswählen"
#: lib/mv_web/live/user_live/form.ex:27
#: lib/mv_web/live/user_live/form.ex:74
#, elixir-autogen, elixir-format
msgid "Set Password"
msgstr "Passwort setzen"
#: lib/mv_web/live/user_live/form.ex:83
#: lib/mv_web/live/user_live/form.ex:130
#, elixir-autogen, elixir-format
msgid "User will be created without a password. Check 'Set Password' to add one."
msgstr "Benutzer wird ohne Passwort erstellt. Aktivieren Sie 'Passwort setzen', um eines hinzuzufügen."

View file

@ -17,13 +17,13 @@ msgid "Actions"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:77
#: lib/mv_web/live/user_live/index.html.heex:69
#: lib/mv_web/live/user_live/index.html.heex:70
#, elixir-autogen, elixir-format
msgid "Are you sure?"
msgstr ""
#: lib/mv_web/components/layouts.ex:71
#: lib/mv_web/components/layouts.ex:83
#: lib/mv_web/components/layouts.ex:72
#: lib/mv_web/components/layouts.ex:84
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
@ -36,14 +36,14 @@ msgid "City"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:79
#: lib/mv_web/live/user_live/index.html.heex:71
#: lib/mv_web/live/user_live/index.html.heex:72
#, elixir-autogen, elixir-format
msgid "Delete"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:71
#: lib/mv_web/live/user_live/form.ex:109
#: lib/mv_web/live/user_live/index.html.heex:63
#: lib/mv_web/live/user_live/form.ex:156
#: lib/mv_web/live/user_live/index.html.heex:64
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
@ -58,7 +58,7 @@ msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:58
#: lib/mv_web/live/member_live/show.ex:27
#: lib/mv_web/live/user_live/form.ex:14
#: lib/mv_web/live/user_live/index.html.heex:48
#: lib/mv_web/live/user_live/index.html.heex:44
#: lib/mv_web/live/user_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Email"
@ -89,17 +89,17 @@ msgid "New Member"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:68
#: lib/mv_web/live/user_live/index.html.heex:60
#: lib/mv_web/live/user_live/index.html.heex:61
#, elixir-autogen, elixir-format
msgid "Show"
msgstr ""
#: lib/mv_web/components/layouts.ex:78
#: lib/mv_web/components/layouts.ex:79
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/mv_web/components/layouts.ex:66
#: lib/mv_web/components/layouts.ex:67
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
@ -168,7 +168,7 @@ msgstr ""
#: lib/mv_web/live/member_live/form.ex:49
#: lib/mv_web/live/property_live/form.ex:41
#: lib/mv_web/live/property_type_live/form.ex:29
#: lib/mv_web/live/user_live/form.ex:92
#: lib/mv_web/live/user_live/form.ex:139
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
@ -262,7 +262,7 @@ msgstr ""
#: lib/mv_web/live/member_live/form.ex:52
#: lib/mv_web/live/property_live/form.ex:44
#: lib/mv_web/live/property_type_live/form.ex:32
#: lib/mv_web/live/user_live/form.ex:95
#: lib/mv_web/live/user_live/form.ex:142
#, elixir-autogen, elixir-format
msgid "Cancel"
msgstr ""
@ -287,7 +287,7 @@ msgstr ""
msgid "Edit User"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:27
#: lib/mv_web/live/user_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Enabled"
msgstr ""
@ -302,29 +302,81 @@ msgstr ""
msgid "Immutable"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex:73
#, elixir-autogen, elixir-format
#: lib/mv_web/components/layouts/navbar.ex:75
#, elixir-autogen
msgid "Logout"
msgstr ""
#: lib/mv_web/live/user_live/index.ex:12
#: lib/mv_web/live/user_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "Listing Users"
msgstr ""
#: lib/mv_web/live/property_live/form.ex:27
#, elixir-autogen, elixir-format
msgid "Member"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex:14
#: lib/mv_web/components/layouts/navbar.ex:16
#: lib/mv_web/live/member_live/index.ex:12
#: lib/mv_web/live/member_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "Members"
msgstr ""
#: lib/mv_web/live/property_live/form.ex:27
#: lib/mv_web/live/user_live/index.html.heex:52
#: lib/mv_web/live/user_live/show.ex:25
#, elixir-autogen, elixir-format
msgid "Member"
msgstr ""
#: lib/mv_web/live/user_live/index.html.heex:55
#, elixir-autogen
msgid "No member"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:31
#, elixir-autogen
msgid "No member assigned"
msgstr ""
msgid "Profile"
msgstr ""
msgid "Edit your member information"
msgstr ""
msgid "Save Profile"
msgstr ""
msgid "Profile updated successfully"
msgstr ""
msgid "No member profile found"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:18
#, elixir-autogen
msgid "Member Assignment"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:31
#, elixir-autogen
msgid "Create new member automatically"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:46
#, elixir-autogen
msgid "Assign to existing member"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:54
#, elixir-autogen
msgid "Select Member"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:57
#, elixir-autogen
msgid "Choose a member..."
msgstr ""
#: lib/mv_web/live/user_live/index.ex:15
#: lib/mv_web/live/user_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "Listing Users"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:50
#: lib/mv_web/live/property_type_live/form.ex:16
#, elixir-autogen, elixir-format
@ -336,29 +388,29 @@ msgstr ""
msgid "New User"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:27
#: lib/mv_web/live/user_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Not enabled"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:25
#: lib/mv_web/live/user_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "Not set"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:75
#: lib/mv_web/live/user_live/form.ex:83
#: lib/mv_web/live/user_live/form.ex:122
#: lib/mv_web/live/user_live/form.ex:130
#, elixir-autogen, elixir-format
msgid "Note"
msgstr ""
#: lib/mv_web/live/user_live/index.html.heex:56
#: lib/mv_web/live/user_live/show.ex:25
#: lib/mv_web/live/user_live/index.html.heex:57
#: lib/mv_web/live/user_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "OIDC ID"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:26
#: lib/mv_web/live/user_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "Password Authentication"
msgstr ""
@ -368,7 +420,7 @@ msgstr ""
msgid "Please select a property type first"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex:69
#: lib/mv_web/components/layouts/navbar.ex:71
#, elixir-autogen, elixir-format
msgid "Profil"
msgstr ""
@ -413,17 +465,17 @@ msgstr ""
msgid "Select member"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex:72
#: lib/mv_web/components/layouts/navbar.ex:74
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:93
#: lib/mv_web/live/user_live/form.ex:140
#, elixir-autogen, elixir-format
msgid "Save User"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:38
#: lib/mv_web/live/user_live/show.ex:50
#, elixir-autogen, elixir-format
msgid "Show User"
msgstr ""
@ -453,7 +505,7 @@ msgstr ""
msgid "Use this form to manage user records in your database."
msgstr ""
#: lib/mv_web/live/user_live/form.ex:110
#: lib/mv_web/live/user_live/form.ex:157
#: lib/mv_web/live/user_live/show.ex:9
#, elixir-autogen, elixir-format
msgid "User"
@ -479,77 +531,77 @@ msgstr ""
msgid "descending"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:109
#: lib/mv_web/live/user_live/form.ex:156
#, elixir-autogen, elixir-format
msgid "New"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:64
#: lib/mv_web/live/user_live/form.ex:111
#, elixir-autogen, elixir-format
msgid "Admin Note"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:64
#: lib/mv_web/live/user_live/form.ex:111
#, elixir-autogen, elixir-format
msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
msgstr ""
#: lib/mv_web/live/user_live/form.ex:55
#: lib/mv_web/live/user_live/form.ex:102
#, elixir-autogen, elixir-format
msgid "At least 8 characters"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:27
#: lib/mv_web/live/user_live/form.ex:74
#, elixir-autogen, elixir-format
msgid "Change Password"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:75
#: lib/mv_web/live/user_live/form.ex:122
#, elixir-autogen, elixir-format
msgid "Check 'Change Password' above to set a new password for this user."
msgstr ""
#: lib/mv_web/live/user_live/form.ex:45
#: lib/mv_web/live/user_live/form.ex:92
#, elixir-autogen, elixir-format
msgid "Confirm Password"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:57
#: lib/mv_web/live/user_live/form.ex:104
#, elixir-autogen, elixir-format
msgid "Consider using special characters"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:56
#: lib/mv_web/live/user_live/form.ex:103
#, elixir-autogen, elixir-format
msgid "Include both letters and numbers"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:35
#: lib/mv_web/live/user_live/form.ex:82
#, elixir-autogen, elixir-format
msgid "Password"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:53
#: lib/mv_web/live/user_live/form.ex:100
#, elixir-autogen, elixir-format
msgid "Password requirements"
msgstr ""
#: lib/mv_web/live/user_live/index.html.heex:25
#: lib/mv_web/live/user_live/index.html.heex:21
#, elixir-autogen, elixir-format
msgid "Select all users"
msgstr ""
#: lib/mv_web/live/user_live/index.html.heex:39
#: lib/mv_web/live/user_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Select user"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:27
#: lib/mv_web/live/user_live/form.ex:74
#, elixir-autogen, elixir-format
msgid "Set Password"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:83
#: lib/mv_web/live/user_live/form.ex:130
#, elixir-autogen, elixir-format
msgid "User will be created without a password. Check 'Set Password' to add one."
msgstr ""

View file

@ -17,13 +17,13 @@ msgid "Actions"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:77
#: lib/mv_web/live/user_live/index.html.heex:69
#: lib/mv_web/live/user_live/index.html.heex:70
#, elixir-autogen, elixir-format
msgid "Are you sure?"
msgstr ""
#: lib/mv_web/components/layouts.ex:71
#: lib/mv_web/components/layouts.ex:83
#: lib/mv_web/components/layouts.ex:72
#: lib/mv_web/components/layouts.ex:84
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
@ -36,14 +36,14 @@ msgid "City"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:79
#: lib/mv_web/live/user_live/index.html.heex:71
#: lib/mv_web/live/user_live/index.html.heex:72
#, elixir-autogen, elixir-format
msgid "Delete"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:71
#: lib/mv_web/live/user_live/form.ex:109
#: lib/mv_web/live/user_live/index.html.heex:63
#: lib/mv_web/live/user_live/form.ex:156
#: lib/mv_web/live/user_live/index.html.heex:64
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
@ -58,7 +58,7 @@ msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:58
#: lib/mv_web/live/member_live/show.ex:27
#: lib/mv_web/live/user_live/form.ex:14
#: lib/mv_web/live/user_live/index.html.heex:48
#: lib/mv_web/live/user_live/index.html.heex:44
#: lib/mv_web/live/user_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Email"
@ -89,17 +89,17 @@ msgid "New Member"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:68
#: lib/mv_web/live/user_live/index.html.heex:60
#: lib/mv_web/live/user_live/index.html.heex:61
#, elixir-autogen, elixir-format
msgid "Show"
msgstr ""
#: lib/mv_web/components/layouts.ex:78
#: lib/mv_web/components/layouts.ex:79
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/mv_web/components/layouts.ex:66
#: lib/mv_web/components/layouts.ex:67
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
@ -168,7 +168,7 @@ msgstr ""
#: lib/mv_web/live/member_live/form.ex:49
#: lib/mv_web/live/property_live/form.ex:41
#: lib/mv_web/live/property_type_live/form.ex:29
#: lib/mv_web/live/user_live/form.ex:92
#: lib/mv_web/live/user_live/form.ex:139
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
@ -262,7 +262,7 @@ msgstr ""
#: lib/mv_web/live/member_live/form.ex:52
#: lib/mv_web/live/property_live/form.ex:44
#: lib/mv_web/live/property_type_live/form.ex:32
#: lib/mv_web/live/user_live/form.ex:95
#: lib/mv_web/live/user_live/form.ex:142
#, elixir-autogen, elixir-format
msgid "Cancel"
msgstr ""
@ -287,7 +287,7 @@ msgstr ""
msgid "Edit User"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:27
#: lib/mv_web/live/user_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Enabled"
msgstr ""
@ -302,27 +302,81 @@ msgstr ""
msgid "Immutable"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex:73
#, elixir-autogen, elixir-format
#: lib/mv_web/components/layouts/navbar.ex:75
#, elixir-autogen
msgid "Logout"
msgstr ""
msgstr "Logout"
#: lib/mv_web/live/user_live/index.ex:12
#: lib/mv_web/live/user_live/index.html.heex:3
#, elixir-autogen, elixir-format, fuzzy
msgid "Listing Users"
msgstr ""
#: lib/mv_web/live/property_live/form.ex:27
#, elixir-autogen, elixir-format
msgid "Member"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex:14
#: lib/mv_web/components/layouts/navbar.ex:16
#: lib/mv_web/live/member_live/index.ex:12
#: lib/mv_web/live/member_live/index.html.heex:3
#, elixir-autogen, elixir-format
msgid "Members"
msgstr "Members"
#: lib/mv_web/live/property_live/form.ex:27
#: lib/mv_web/live/user_live/index.html.heex:52
#: lib/mv_web/live/user_live/show.ex:25
#, elixir-autogen, elixir-format
msgid "Member"
msgstr "Member"
#: lib/mv_web/live/user_live/index.html.heex:55
#, elixir-autogen
msgid "No member"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:31
#, elixir-autogen
msgid "No member assigned"
msgstr ""
#, fuzzy
msgid "Profile"
msgstr "Profile"
msgid "Edit your member information"
msgstr ""
#, fuzzy
msgid "Save Profile"
msgstr "Profile"
msgid "Profile updated successfully"
msgstr ""
msgid "No member profile found"
msgstr "No member profile found"
#: lib/mv_web/live/user_live/form.ex:18
#, elixir-autogen
msgid "Member Assignment"
msgstr "Member Assignment"
#: lib/mv_web/live/user_live/form.ex:31
#, elixir-autogen
msgid "Create new member automatically"
msgstr "Create new member automatically"
#: lib/mv_web/live/user_live/form.ex:46
#, elixir-autogen
msgid "Assign to existing member"
msgstr "Assign to existing member"
#: lib/mv_web/live/user_live/form.ex:54
#, elixir-autogen
msgid "Select Member"
msgstr "Select Member"
#: lib/mv_web/live/user_live/form.ex:57
#, elixir-autogen
msgid "Choose a member..."
msgstr "Choose a member..."
#: lib/mv_web/live/user_live/index.ex:15
#: lib/mv_web/live/user_live/index.html.heex:3
#, elixir-autogen, elixir-format, fuzzy
msgid "Listing Users"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:50
@ -336,29 +390,29 @@ msgstr ""
msgid "New User"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:27
#: lib/mv_web/live/user_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Not enabled"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:25
#: lib/mv_web/live/user_live/show.ex:33
#, elixir-autogen, elixir-format, fuzzy
msgid "Not set"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:75
#: lib/mv_web/live/user_live/form.ex:83
#: lib/mv_web/live/user_live/form.ex:122
#: lib/mv_web/live/user_live/form.ex:130
#, elixir-autogen, elixir-format, fuzzy
msgid "Note"
msgstr ""
#: lib/mv_web/live/user_live/index.html.heex:56
#: lib/mv_web/live/user_live/show.ex:25
#: lib/mv_web/live/user_live/index.html.heex:57
#: lib/mv_web/live/user_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "OIDC ID"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:26
#: lib/mv_web/live/user_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "Password Authentication"
msgstr ""
@ -368,10 +422,10 @@ msgstr ""
msgid "Please select a property type first"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex:69
#: lib/mv_web/components/layouts/navbar.ex:71
#, elixir-autogen, elixir-format
msgid "Profil"
msgstr ""
msgstr "Profile"
#: lib/mv_web/live/property_live/form.ex:207
#, elixir-autogen, elixir-format, fuzzy
@ -411,19 +465,19 @@ msgstr ""
#: lib/mv_web/live/member_live/index.html.heex:41
#, elixir-autogen, elixir-format
msgid "Select member"
msgstr ""
msgstr "Select Member"
#: lib/mv_web/components/layouts/navbar.ex:72
#: lib/mv_web/components/layouts/navbar.ex:74
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:93
#: lib/mv_web/live/user_live/form.ex:140
#, elixir-autogen, elixir-format, fuzzy
msgid "Save User"
msgstr ""
#: lib/mv_web/live/user_live/show.ex:38
#: lib/mv_web/live/user_live/show.ex:50
#, elixir-autogen, elixir-format, fuzzy
msgid "Show User"
msgstr ""
@ -453,7 +507,7 @@ msgstr ""
msgid "Use this form to manage user records in your database."
msgstr ""
#: lib/mv_web/live/user_live/form.ex:110
#: lib/mv_web/live/user_live/form.ex:157
#: lib/mv_web/live/user_live/show.ex:9
#, elixir-autogen, elixir-format
msgid "User"
@ -479,77 +533,77 @@ msgstr ""
msgid "descending"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:109
#: lib/mv_web/live/user_live/form.ex:156
#, elixir-autogen, elixir-format
msgid "New"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:64
#: lib/mv_web/live/user_live/form.ex:111
#, elixir-autogen, elixir-format
msgid "Admin Note"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:64
#: lib/mv_web/live/user_live/form.ex:111
#, elixir-autogen, elixir-format
msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
msgstr "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
#: lib/mv_web/live/user_live/form.ex:55
#: lib/mv_web/live/user_live/form.ex:102
#, elixir-autogen, elixir-format
msgid "At least 8 characters"
msgstr "At least 8 characters"
#: lib/mv_web/live/user_live/form.ex:27
#: lib/mv_web/live/user_live/form.ex:74
#, elixir-autogen, elixir-format
msgid "Change Password"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:75
#: lib/mv_web/live/user_live/form.ex:122
#, elixir-autogen, elixir-format
msgid "Check 'Change Password' above to set a new password for this user."
msgstr "Check 'Change Password' above to set a new password for this user."
#: lib/mv_web/live/user_live/form.ex:45
#: lib/mv_web/live/user_live/form.ex:92
#, elixir-autogen, elixir-format
msgid "Confirm Password"
msgstr "Confirm Password"
#: lib/mv_web/live/user_live/form.ex:57
#: lib/mv_web/live/user_live/form.ex:104
#, elixir-autogen, elixir-format
msgid "Consider using special characters"
msgstr "Consider using special characters"
#: lib/mv_web/live/user_live/form.ex:56
#: lib/mv_web/live/user_live/form.ex:103
#, elixir-autogen, elixir-format
msgid "Include both letters and numbers"
msgstr "Include both letters and numbers"
#: lib/mv_web/live/user_live/form.ex:35
#: lib/mv_web/live/user_live/form.ex:82
#, elixir-autogen, elixir-format
msgid "Password"
msgstr "Password"
#: lib/mv_web/live/user_live/form.ex:53
#: lib/mv_web/live/user_live/form.ex:100
#, elixir-autogen, elixir-format
msgid "Password requirements"
msgstr "Password requirements"
#: lib/mv_web/live/user_live/index.html.heex:25
#: lib/mv_web/live/user_live/index.html.heex:21
#, elixir-autogen, elixir-format, fuzzy
msgid "Select all users"
msgstr ""
#: lib/mv_web/live/user_live/index.html.heex:39
#: lib/mv_web/live/user_live/index.html.heex:35
#, elixir-autogen, elixir-format, fuzzy
msgid "Select user"
msgstr ""
#: lib/mv_web/live/user_live/form.ex:27
#: lib/mv_web/live/user_live/form.ex:74
#, elixir-autogen, elixir-format
msgid "Set Password"
msgstr "Set Password"
#: lib/mv_web/live/user_live/form.ex:83
#: lib/mv_web/live/user_live/form.ex:130
#, elixir-autogen, elixir-format
msgid "User will be created without a password. Check 'Set Password' to add one."
msgstr "User will be created without a password. Check 'Set Password' to add one."

View file

@ -0,0 +1,17 @@
defmodule Mv.Repo.Migrations.AddUniqueMemberId do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
create unique_index(:users, [:member_id], name: "users_unique_member_id_index")
end
def down do
drop_if_exists unique_index(:users, [:member_id], name: "users_unique_member_id_index")
end
end

View file

@ -0,0 +1,21 @@
defmodule Mv.Repo.Migrations.AddAdminToUsers do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:users) do
add :admin?, :boolean, null: false, default: false
end
end
def down do
alter table(:users) do
remove :admin?
end
end
end

View file

@ -0,0 +1,141 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "email",
"type": "citext"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "hashed_password",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "oidc_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "users_member_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "members"
},
"scale": null,
"size": null,
"source": "member_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "5D2170BB579B2CE45596ED1CED44D45A3BBEDDB7B9573CB7E5F087F47F4C4CA9",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_email_index",
"keys": [
{
"type": "atom",
"value": "email"
}
],
"name": "unique_email",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_oidc_id_index",
"keys": [
{
"type": "atom",
"value": "oidc_id"
}
],
"name": "unique_oidc_id",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_member_id_index",
"keys": [
{
"type": "atom",
"value": "member_id"
}
],
"name": "unique_member_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "users"
}

View file

@ -0,0 +1,153 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "email",
"type": "citext"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "hashed_password",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "oidc_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "false",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "admin?",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "users_member_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "members"
},
"scale": null,
"size": null,
"source": "member_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "045ACBFE7FEA8E44855F93AE816A54CE6302371FEA43C8865288B86E6F908838",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_email_index",
"keys": [
{
"type": "atom",
"value": "email"
}
],
"name": "unique_email",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_member_id_index",
"keys": [
{
"type": "atom",
"value": "member_id"
}
],
"name": "unique_member_id",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_oidc_id_index",
"keys": [
{
"type": "atom",
"value": "oidc_id"
}
],
"name": "unique_oidc_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "users"
}

View file

@ -0,0 +1,107 @@
defmodule Mv.Accounts.RegistrationMemberTest do
use Mv.DataCase
alias Mv.Accounts
alias Mv.Accounts.User.MemberCreationNotifier
describe "registration creates member" do
test "registering a user creates and assigns a new member via notifier" do
# Create user first
assert {:ok, user} =
Accounts.register_with_password(%{
email: "test@example.com",
password: "password123"
})
assert to_string(user.email) == "test@example.com"
# Manually trigger the notifier to test synchronously
notification = %Ash.Notifier.Notification{
action: %{name: :register_with_password},
resource: Mv.Accounts.User,
data: user
}
assert :ok = MemberCreationNotifier.notify(notification)
# Reload user to get updated member_id
user = Ash.reload!(user, domain: Mv.Accounts)
# User should have a member assigned
assert user.member_id != nil
# Member should exist and have correct data
member = Ash.get!(Mv.Membership.Member, user.member_id, domain: Mv.Membership)
assert member.email == "test@example.com"
assert member.first_name == "User"
assert member.last_name == "Generated"
end
test "notifier doesn't create member if user already has one" do
# Create a member first
{:ok, existing_member} =
Mv.Membership.create_member(%{
email: "existing@example.com",
first_name: "Existing",
last_name: "Member"
})
# Create user with existing member
assert {:ok, user} =
Accounts.create_user(%{
email: "existing@example.com",
member_id: existing_member.id
})
# Trigger notifier
notification = %Ash.Notifier.Notification{
action: %{name: :create_user},
resource: Mv.Accounts.User,
data: user
}
assert :ok = MemberCreationNotifier.notify(notification)
# User should still have the same member
user = Ash.reload!(user, domain: Mv.Accounts)
assert user.member_id == existing_member.id
# Should not have created a new member
member = Ash.get!(Mv.Membership.Member, user.member_id, domain: Mv.Membership)
assert member.first_name == "Existing"
assert member.last_name == "Member"
end
test "member data has correct default values" do
# Create user
assert {:ok, user} =
Accounts.create_user(%{
email: "member@example.com"
})
# Trigger notifier manually
notification = %Ash.Notifier.Notification{
action: %{name: :create_user},
resource: Mv.Accounts.User,
data: user
}
assert :ok = MemberCreationNotifier.notify(notification)
# Reload and check member
user = Ash.reload!(user, domain: Mv.Accounts)
member = Ash.get!(Mv.Membership.Member, user.member_id, domain: Mv.Membership)
# Email should match user
assert member.email == "member@example.com"
# Other fields should have default values
assert member.first_name == "User"
assert member.last_name == "Generated"
# Allow both false and nil
assert member.paid == false || member.paid == nil
assert member.phone_number == nil
assert member.birth_date == nil
end
end
end

View file

@ -0,0 +1,20 @@
defmodule Mv.Accounts.UserDeleteMemberTest do
use Mv.DataCase, async: true
alias Mv.Accounts
alias Mv.Membership
describe "user deletion does not delete member" do
test "deleting a user keeps the member in the system" do
{:ok, member} =
Membership.create_member(%{
first_name: "Keep",
last_name: "Me",
email: "keepme@example.com"
})
{:ok, user} = Accounts.create_user(%{email: "keepuser@example.com", member_id: member.id})
:ok = Accounts.destroy_user(user)
assert Membership.get_member!(member.id)
end
end
end

View file

@ -0,0 +1,77 @@
defmodule Mv.Accounts.UserMemberIntegrationTest do
use Mv.DataCase, async: true
alias Mv.Accounts
alias Mv.Membership
alias Mv.Accounts.User.MemberCreationNotifier
describe "User-Member-Relation" do
test "ein User kann einem Member zugeordnet werden" do
{:ok, member} =
Membership.create_member(%{
first_name: "Max",
last_name: "Mustermann",
email: "max@example.com"
})
{:ok, user} = Accounts.create_user(%{email: "user1@example.com", member_id: member.id})
assert user.member_id == member.id
end
test "ein Member kann nur einem User zugeordnet werden (unique constraint)" do
{:ok, member} =
Membership.create_member(%{
first_name: "Anna",
last_name: "Test",
email: "anna@example.com"
})
{:ok, user1} = Accounts.create_user(%{email: "user2@example.com", member_id: member.id})
assert user1.member_id == member.id
{:error, %Ash.Error.Invalid{errors: errors}} =
Accounts.create_user(%{email: "user3@example.com", member_id: member.id})
assert Enum.any?(errors, fn error ->
error.message =~ "already been taken" or error.field == :member_id
end)
end
test "ein User ohne Member ist nicht erlaubt (bei Registrierung/Erstellung)" do
# Create user without member first
result = Accounts.create_user(%{email: "user4@example.com"})
case result do
{:ok, user} ->
# User is created but doesn't have member yet
assert user.member_id == nil
# Manually trigger the notifier to simulate automatic member creation
notification = %Ash.Notifier.Notification{
action: %{name: :create_user},
resource: Mv.Accounts.User,
data: user
}
assert :ok = MemberCreationNotifier.notify(notification)
# Reload user and verify member was created and assigned
user = Ash.reload!(user, domain: Mv.Accounts)
assert user.member_id, "User should have a member_id assigned after notifier"
{:error, _} ->
flunk("User creation should succeed")
end
end
test "ein Member kann ohne User existieren" do
{:ok, member} =
Membership.create_member(%{
first_name: "Lisa",
last_name: "Solo",
email: "lisa@example.com"
})
assert member.id
end
end
end

View file

@ -119,8 +119,10 @@ defmodule MvWeb.OidcIntegrationTest do
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
# Check for either Required error or InvalidAttribute error with email field
assert Enum.any?(errors, fn err ->
match?(%Ash.Error.Changes.Required{field: :email}, err)
match?(%Ash.Error.Changes.Required{field: :email}, err) or
match?(%Ash.Error.Changes.InvalidAttribute{field: :email}, err)
end)
end

View file

@ -0,0 +1,81 @@
defmodule MvWeb.UserLive.AdminMemberAssignmentTest do
use MvWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "admin can assign existing member without user to new user", %{conn: conn} do
{:ok, member} =
Mv.Membership.create_member(%{
first_name: "Admin",
last_name: "Choice",
email: "adminchoice@example.com"
})
conn = conn_with_oidc_user(conn, %{email: "admin@example.com"})
{:ok, view, _html} = live(conn, "/users/new")
# First click the "assign existing member" radio button
view
|> element("input[value='assign_existing']")
|> render_click()
# Then submit the form with member_id
view
|> form("#user-form", user: %{email: "adminuser@example.com", member_id: member.id})
|> render_submit()
user =
Ash.get!(Mv.Accounts.User, [email: Ash.CiString.new("adminuser@example.com")],
domain: Mv.Accounts
)
assert user.member_id == member.id
end
test "admin can create new member for user if none selected", %{conn: conn} do
conn = conn_with_oidc_user(conn, %{email: "admin2@example.com"})
{:ok, view, _html} = live(conn, "/users/new")
# Default mode is "create_new", so just submit
view
|> form("#user-form", user: %{email: "adminnewmember@example.com"})
|> render_submit()
user =
Ash.get!(Mv.Accounts.User, [email: Ash.CiString.new("adminnewmember@example.com")],
domain: Mv.Accounts
)
assert user.member_id
{:ok, member} = Mv.Membership.get_member!(user.member_id)
assert member.email == "adminnewmember@example.com"
end
test "admin cannot assign member that already has a user", %{conn: conn} do
{:ok, member} =
Mv.Membership.create_member(%{
first_name: "Taken",
last_name: "Member",
email: "taken@example.com"
})
{:ok, _user} =
Mv.Accounts.create_user(%{email: "takenuser@example.com", member_id: member.id})
conn = conn_with_oidc_user(conn, %{email: "admin3@example.com"})
{:ok, view, _html} = live(conn, "/users/new")
# First click the "assign existing member" radio button
view
|> element("input[value='assign_existing']")
|> render_click()
# Check that the already assigned member is NOT in the dropdown options
html = render(view)
# Member should not be available for selection
refute html =~ "Taken Member"
# The dropdown should have limited or no options since member is already taken
# Placeholder should be visible
assert html =~ "Choose a member..."
end
end

View file

@ -244,7 +244,9 @@ defmodule MvWeb.UserLive.FormTest do
flunk("Expected validation error but form was submitted successfully")
html when is_binary(html) ->
assert html =~ "must have length of at least 8"
# Allow for different validation message sources
assert html =~ "length must be greater than or equal to 8" or
html =~ "must have length of at least 8"
end
end
end

View file

@ -0,0 +1,36 @@
defmodule MvWeb.UserLive.MemberDisplayTest do
use MvWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "User-Liste zeigt zugeordneten Member an", %{conn: conn} do
{:ok, member} =
Mv.Membership.create_member(%{
first_name: "Max",
last_name: "Mustermann",
email: "max@example.com"
})
{:ok, _user} = Mv.Accounts.create_user(%{email: "user5@example.com", member_id: member.id})
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, "/users")
assert html =~ "Max Mustermann"
# User email, not member email
assert html =~ "user5@example.com"
end
test "User-Detailansicht zeigt Member-Daten an", %{conn: conn} do
{:ok, member} =
Mv.Membership.create_member(%{
first_name: "Anna",
last_name: "Test",
email: "anna@example.com"
})
{:ok, user} = Mv.Accounts.create_user(%{email: "user6@example.com", member_id: member.id})
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, "/users/#{user.id}")
assert html =~ "Anna Test"
# In detail view, member email should be shown
assert html =~ "anna@example.com"
end
end