diff --git a/.env.example b/.env.example index 04e9dbd..c9cc51e 100644 --- a/.env.example +++ b/.env.example @@ -36,3 +36,4 @@ ASSOCIATION_NAME="Sportsclub XYZ" # VEREINFACHT_API_URL=https://api.verein.visuel.dev/api/v1 # VEREINFACHT_API_KEY=your-api-key # VEREINFACHT_CLUB_ID=2 +# VEREINFACHT_APP_URL=https://app.verein.visuel.dev diff --git a/lib/membership/setting.ex b/lib/membership/setting.ex index 40ef985..33445d3 100644 --- a/lib/membership/setting.ex +++ b/lib/membership/setting.ex @@ -72,7 +72,8 @@ defmodule Mv.Membership.Setting do :default_membership_fee_type_id, :vereinfacht_api_url, :vereinfacht_api_key, - :vereinfacht_club_id + :vereinfacht_club_id, + :vereinfacht_app_url ] end @@ -87,7 +88,8 @@ defmodule Mv.Membership.Setting do :default_membership_fee_type_id, :vereinfacht_api_url, :vereinfacht_api_key, - :vereinfacht_club_id + :vereinfacht_club_id, + :vereinfacht_app_url ] end @@ -251,6 +253,13 @@ defmodule Mv.Membership.Setting do description "Vereinfacht club ID for multi-tenancy" end + attribute :vereinfacht_app_url, :string do + allow_nil? true + public? true + + description "Vereinfacht app base URL for contact view links (e.g. https://app.verein.visuel.dev)" + end + timestamps() end diff --git a/lib/mv/config.ex b/lib/mv/config.ex index f6f6ec7..d2ad66c 100644 --- a/lib/mv/config.ex +++ b/lib/mv/config.ex @@ -178,6 +178,37 @@ defmodule Mv.Config do env_or_setting("VEREINFACHT_CLUB_ID", :vereinfacht_club_id) end + @doc """ + Returns the Vereinfacht app base URL for contact view links (frontend, not API). + + Reads from `VEREINFACHT_APP_URL` env first, then from Settings. + Used to build links like https://app.verein.visuel.dev/en/admin/finances/contacts/{id}. + If not set, derived from API URL by replacing host \"api.\" with \"app.\" when possible. + """ + @spec vereinfacht_app_url() :: String.t() | nil + def vereinfacht_app_url do + env_or_setting("VEREINFACHT_APP_URL", :vereinfacht_app_url) || + derive_app_url_from_api_url(vereinfacht_api_url()) + end + + defp derive_app_url_from_api_url(nil), do: nil + + defp derive_app_url_from_api_url(api_url) when is_binary(api_url) do + api_url = String.trim(api_url) + uri = URI.parse(api_url) + host = uri.host || "" + + if String.starts_with?(host, "api.") do + app_host = "app." <> String.slice(host, 4..-1//1) + scheme = uri.scheme || "https" + "#{scheme}://#{app_host}" + else + nil + end + end + + defp derive_app_url_from_api_url(_), do: nil + @doc """ Returns true if Vereinfacht is fully configured (URL, API key, and club ID all set). """ @@ -211,6 +242,11 @@ defmodule Mv.Config do """ def vereinfacht_club_id_env_set?, do: env_set?("VEREINFACHT_CLUB_ID") + @doc """ + Returns true if VEREINFACHT_APP_URL is set (field is read-only in Settings). + """ + def vereinfacht_app_url_env_set?, do: env_set?("VEREINFACHT_APP_URL") + defp env_set?(key) do case System.get_env(key) do nil -> false @@ -241,18 +277,22 @@ defmodule Mv.Config do end @doc """ - Returns the URL to view a finance contact (e.g. in Vereinfacht frontend or API). + Returns the URL to view a finance contact in the Vereinfacht app (frontend). - Uses the configured API base URL and appends /finance-contacts/{id}. - Can be extended later with a dedicated frontend URL setting. + Uses the configured app base URL (or derived from API URL) and appends + /en/admin/finances/contacts/{id}. Returns nil if no app URL can be determined. """ @spec vereinfacht_contact_view_url(String.t()) :: String.t() | nil def vereinfacht_contact_view_url(contact_id) when is_binary(contact_id) do - base = vereinfacht_api_url() + base = vereinfacht_app_url() - if present?(base), - do: base |> String.trim_trailing("/") |> then(&"#{&1}/finance-contacts/#{contact_id}"), - else: nil + if present?(base) do + base + |> String.trim_trailing("/") + |> then(&"#{&1}/en/admin/finances/contacts/#{contact_id}") + else + nil + end end defp present?(nil), do: false diff --git a/lib/mv_web/live/global_settings_live.ex b/lib/mv_web/live/global_settings_live.ex index 3da4aa6..b841931 100644 --- a/lib/mv_web/live/global_settings_live.ex +++ b/lib/mv_web/live/global_settings_live.ex @@ -48,6 +48,7 @@ defmodule MvWeb.GlobalSettingsLive do |> assign(:vereinfacht_api_url_env_set, Mv.Config.vereinfacht_api_url_env_set?()) |> assign(:vereinfacht_api_key_env_set, Mv.Config.vereinfacht_api_key_env_set?()) |> assign(:vereinfacht_club_id_env_set, Mv.Config.vereinfacht_club_id_env_set?()) + |> assign(:vereinfacht_app_url_env_set, Mv.Config.vereinfacht_app_url_env_set?()) |> assign(:vereinfacht_api_key_set, present?(settings.vereinfacht_api_key)) |> assign(:last_vereinfacht_sync_result, nil) |> assign_form() @@ -142,6 +143,18 @@ defmodule MvWeb.GlobalSettingsLive do if(@vereinfacht_club_id_env_set, do: gettext("From VEREINFACHT_CLUB_ID"), else: "2") } /> + <.input + field={@form[:vereinfacht_app_url]} + type="text" + label={gettext("App URL (contact view link)")} + disabled={@vereinfacht_app_url_env_set} + placeholder={ + if(@vereinfacht_app_url_env_set, + do: gettext("From VEREINFACHT_APP_URL"), + else: "https://app.verein.visuel.dev" + ) + } + /> <.button :if={ diff --git a/priv/repo/migrations/20260218190000_add_vereinfacht_app_url.exs b/priv/repo/migrations/20260218190000_add_vereinfacht_app_url.exs new file mode 100644 index 0000000..f10728c --- /dev/null +++ b/priv/repo/migrations/20260218190000_add_vereinfacht_app_url.exs @@ -0,0 +1,15 @@ +defmodule Mv.Repo.Migrations.AddVereinfachtAppUrl do + use Ecto.Migration + + def up do + alter table(:settings) do + add :vereinfacht_app_url, :text + end + end + + def down do + alter table(:settings) do + remove :vereinfacht_app_url + end + end +end diff --git a/test/mv/config_vereinfacht_test.exs b/test/mv/config_vereinfacht_test.exs index 08b8104..07260c2 100644 --- a/test/mv/config_vereinfacht_test.exs +++ b/test/mv/config_vereinfacht_test.exs @@ -39,14 +39,23 @@ defmodule Mv.ConfigVereinfachtTest do assert Mv.Config.vereinfacht_contact_view_url("123") == nil end - test "returns URL when API URL is set" do + test "returns app contact view URL when API URL is set (derived app URL)" do set_vereinfacht_env("VEREINFACHT_API_URL", "https://api.example.com/api/v1") assert Mv.Config.vereinfacht_contact_view_url("42") == - "https://api.example.com/api/v1/finance-contacts/42" + "https://app.example.com/en/admin/finances/contacts/42" after clear_vereinfacht_env() end + + test "returns app contact view URL when VEREINFACHT_APP_URL is set" do + set_vereinfacht_env("VEREINFACHT_APP_URL", "https://app.verein.visuel.dev") + + assert Mv.Config.vereinfacht_contact_view_url("abc") == + "https://app.verein.visuel.dev/en/admin/finances/contacts/abc" + after + System.delete_env("VEREINFACHT_APP_URL") + end end defp set_vereinfacht_env(key, value) do