diff --git a/core/lib/external_sign_in.ex b/core/lib/external_sign_in.ex
index ee5885c70..410dda794 100644
--- a/core/lib/external_sign_in.ex
+++ b/core/lib/external_sign_in.ex
@@ -11,7 +11,9 @@ defmodule ExternalSignIn do
register_user(organisation, external_id)
end
- CoreWeb.UserAuth.log_in_user_without_redirect(conn, user)
+ conn
+ |> CoreWeb.UserAuth.log_in_user_without_redirect(user)
+ |> Plug.Conn.assign(:current_user, user)
end
def get_user_by_external_id(external_id) do
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-consent.po b/core/priv/gettext/en/LC_MESSAGES/eyra-consent.po
index 514c23985..4434c6ee2 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-consent.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-consent.po
@@ -22,3 +22,11 @@ msgstr "
Write here your custom consent terms and conditions..
"
#, elixir-autogen, elixir-format
msgid "onboarding.consent.checkbox"
msgstr "I have read and agree with the above terms"
+
+#, elixir-autogen, elixir-format
+msgid "locked.error.message"
+msgstr "This version of the consent text has already been signed by participants. Please refresh the page to continue changing the text."
+
+#, elixir-autogen, elixir-format
+msgid "out_of_sync.error.message"
+msgstr "Someone made changes to the consent text. Please refresh the page to continue."
diff --git a/core/priv/gettext/eyra-consent.pot b/core/priv/gettext/eyra-consent.pot
index 3c1fbcac2..d789ea5e9 100644
--- a/core/priv/gettext/eyra-consent.pot
+++ b/core/priv/gettext/eyra-consent.pot
@@ -22,3 +22,11 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "onboarding.consent.checkbox"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "locked.error.message"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "out_of_sync.error.message"
+msgstr ""
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-consent.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-consent.po
index 37ca4e1e4..ae40207e9 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-consent.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-consent.po
@@ -22,3 +22,11 @@ msgstr "Beschrijf hier de consent voorwaarden
"
#, elixir-autogen, elixir-format
msgid "onboarding.consent.checkbox"
msgstr "Ik heb de bovenstaande voorwaarden gelezen en ga ermee akkoord"
+
+#, elixir-autogen, elixir-format
+msgid "locked.error.message"
+msgstr "Deze versie van de consent tekst is al ondertekend door participanten. Ververs de pagina om verdere aanpassingen te maken aan de tekst."
+
+#, elixir-autogen, elixir-format
+msgid "out_of_sync.error.message"
+msgstr "Iemand heeft de consent tekst is aangepast. Ververs de pagina om verder te gaan."
diff --git a/core/systems/assignment/crew_page_builder.ex b/core/systems/assignment/crew_page_builder.ex
index a1d79b86e..1206d2daf 100644
--- a/core/systems/assignment/crew_page_builder.ex
+++ b/core/systems/assignment/crew_page_builder.ex
@@ -13,44 +13,54 @@ defmodule Systems.Assignment.CrewPageBuilder do
end
defp flow(%{status: status} = assignment, assigns) do
- if is_tester?(assignment, assigns) or status == :online do
- flow(assignment, assigns, current_flow(assigns))
+ is_tester? = is_tester?(assignment, assigns)
+
+ if is_tester? or status == :online do
+ flow(assignment, assigns, current_flow(assigns), is_tester?)
else
[]
end
end
- defp flow(assignment, assigns, nil), do: full_flow(assignment, assigns)
+ defp flow(assignment, assigns, nil, is_tester?), do: full_flow(assignment, assigns, is_tester?)
- defp flow(assignment, assigns, current_flow) do
- full_flow(assignment, assigns)
+ defp flow(assignment, assigns, current_flow, is_tester?) do
+ full_flow(assignment, assigns, is_tester?)
|> Enum.filter(fn %{ref: %{id: id}} ->
Enum.find(current_flow, &(&1.ref.id == id)) != nil
end)
end
- defp full_flow(assignment, assigns) do
+ defp full_flow(assignment, assigns, is_tester?) do
[
- consent_view(assignment, assigns),
- work_view(assignment, assigns)
+ consent_view(assignment, assigns, is_tester?),
+ work_view(assignment, assigns, is_tester?)
]
|> Enum.filter(&(&1 != nil))
end
defp current_flow(%{fabric: %{children: children}}), do: children
- defp consent_view(%{consent_agreement: nil}, _), do: nil
+ defp consent_view(%{consent_agreement: nil}, _, _), do: nil
- defp consent_view(%{consent_agreement: consent_agreement}, %{current_user: user, fabric: fabric}) do
- revision = Consent.Public.latest_revision(consent_agreement, [:signatures])
+ defp consent_view(
+ %{consent_agreement: consent_agreement},
+ %{current_user: user, fabric: fabric},
+ is_tester?
+ ) do
+ if Consent.Public.has_signature(consent_agreement, user) and not is_tester? do
+ nil
+ else
+ revision = Consent.Public.latest_revision(consent_agreement, [:signatures])
- Fabric.prepare_child(fabric, :onboarding_view, Assignment.OnboardingConsentView, %{
- revision: revision,
- user: user
- })
+ Fabric.prepare_child(fabric, :onboarding_view, Assignment.OnboardingConsentView, %{
+ revision: revision,
+ user: user
+ })
+ end
end
- defp work_view(assignment, %{fabric: fabric} = assigns) do
+ defp work_view(assignment, %{fabric: fabric} = assigns, _) do
work_items = work_items(assignment, assigns)
Fabric.prepare_child(fabric, :work_view, Assignment.CrewWorkView, %{
@@ -84,7 +94,7 @@ defmodule Systems.Assignment.CrewPageBuilder do
if task = Crew.Public.get_task(crew, identifier) do
task
else
- Crew.Public.create_task(crew, [member], identifier)
+ Crew.Public.create_task!(crew, [member], identifier)
end
end
end
diff --git a/core/systems/assignment/gdpr_form.ex b/core/systems/assignment/gdpr_form.ex
index 856fb6784..fd60d3600 100644
--- a/core/systems/assignment/gdpr_form.ex
+++ b/core/systems/assignment/gdpr_form.ex
@@ -48,7 +48,7 @@ defmodule Systems.Assignment.GdprForm do
%{
module: Consent.RevisionForm,
params: %{
- entity: Consent.Public.latest_unlocked_revision_safe(consent_agreement)
+ entity: Consent.Public.latest_revision(consent_agreement)
}
}
end
diff --git a/core/systems/consent/_public.ex b/core/systems/consent/_public.ex
index 3a16c0ce1..87d95a68f 100644
--- a/core/systems/consent/_public.ex
+++ b/core/systems/consent/_public.ex
@@ -21,12 +21,37 @@ defmodule Systems.Consent.Public do
|> Ecto.Changeset.put_assoc(:auth_node, auth_node)
end
- def create_revision(source, agreement) do
- prepare_revision(source, agreement)
+ def bump_revision_if_needed(agreement_id) when is_integer(agreement_id) do
+ agreement_id
+ |> get_agreement!()
+ |> bump_revision_if_needed()
+ end
+
+ def bump_revision_if_needed(agreement) do
+ Multi.new()
+ |> Multi.run(:revision, fn _, _ ->
+ case latest_revision(agreement, [:signatures]) do
+ nil -> create_revision(agreement, dgettext("eyra-consent", "default.consent.text"))
+ %{source: source, signatures: [_ | _]} -> create_revision(agreement, source)
+ revision -> {:ok, revision}
+ end
+ end)
+ |> Repo.transaction()
+ end
+
+ def bump_revision_if_needed!(agreement) do
+ case bump_revision_if_needed(agreement) do
+ {:ok, %{revision: revision}} -> revision
+ _ -> nil
+ end
+ end
+
+ def create_revision(agreement, source) do
+ prepare_revision(agreement, source)
|> Repo.insert()
end
- def prepare_revision(source, agreement) when is_binary(source) do
+ def prepare_revision(agreement, source) when is_binary(source) do
%Consent.RevisionModel{}
|> Consent.RevisionModel.changeset(%{source: source})
|> Ecto.Changeset.put_assoc(:agreement, agreement)
@@ -52,8 +77,19 @@ defmodule Systems.Consent.Public do
Repo.get!(Consent.RevisionModel, id) |> Repo.preload(preload)
end
- def has_signature(revision, user) do
- get_signature(revision, user) != nil
+ def has_signature(context, user) do
+ get_signature(context, user) != nil
+ end
+
+ def get_signature(%Consent.AgreementModel{id: agreement_id}, %Core.Accounts.User{id: user_id}) do
+ from(s in Consent.SignatureModel,
+ join: r in Consent.RevisionModel,
+ on: r.id == s.revision_id,
+ where: s.user_id == ^user_id,
+ where: r.agreement_id == ^agreement_id
+ )
+ |> Repo.all()
+ |> List.first()
end
def get_signature(%Consent.RevisionModel{id: revision_id}, %Core.Accounts.User{id: user_id}) do
@@ -74,25 +110,6 @@ defmodule Systems.Consent.Public do
|> Repo.all()
end
- def latest_unlocked_revision_safe(agreement, preload \\ []) do
- if revision = latest_unlocked_revision(agreement, preload) do
- revision
- else
- source =
- if revision = latest_revision(agreement, preload) do
- revision.source
- else
- dgettext("eyra-consent", "default.consent.text")
- end
-
- create_revision(source, agreement)
- end
-
- query_unlocked_revisions(agreement, preload)
- |> Repo.all()
- |> List.first()
- end
-
def latest_unlocked_revision(agreement, preload \\ []) do
query_unlocked_revisions(agreement, preload)
|> Repo.all()
@@ -134,13 +151,19 @@ defmodule Systems.Consent.Public do
%Ecto.Changeset{data: %Consent.RevisionModel{id: id, updated_at: updated_at}} = changeset
) do
Multi.new()
- |> Multi.run(:validate_timestamp, fn _, _ ->
- %{updated_at: stored_updated_at} = Consent.Public.get_revision!(id)
+ |> Multi.run(:validate, fn _, _ ->
+ %{updated_at: stored_updated_at, signatures: signatures} =
+ Consent.Public.get_revision!(id, [:signatures])
+
+ cond do
+ stored_updated_at != updated_at ->
+ {:error, :out_of_sync}
- if stored_updated_at == updated_at do
- {:ok, :valid}
- else
- {:error, "Revision out of sync"}
+ not Enum.empty?(signatures) ->
+ {:error, :locked}
+
+ true ->
+ {:ok, :valid}
end
end)
|> Multi.update(:consent_revision, changeset)
@@ -150,10 +173,32 @@ defmodule Systems.Consent.Public do
end
defimpl Core.Persister, for: Systems.Consent.RevisionModel do
+ import CoreWeb.Gettext
+
def save(_revision, changeset) do
case Systems.Consent.Public.update_revision(changeset) do
- {:ok, %{consent_revision: revision}} -> {:ok, revision}
- _ -> {:error, changeset}
+ {:ok, %{consent_revision: revision}} ->
+ {:ok, revision}
+
+ {:error, _, _, _} = error ->
+ {:error, changeset |> handle_error(error)}
end
end
+
+ defp handle_error(changeset, {:error, :validate, :locked, _}) do
+ Systems.Consent.Public.bump_revision_if_needed!(changeset.data.agreement_id)
+
+ changeset
+ |> Ecto.Changeset.add_error(:locked, dgettext("eyra-consent", "locked.error.message"))
+ end
+
+ defp handle_error(changeset, {:error, :validate, :out_of_sync, _}) do
+ changeset
+ |> Ecto.Changeset.add_error(
+ :out_of_sync,
+ dgettext("eyra-consent", "out_of_sync.error.message")
+ )
+ end
+
+ defp handle_error(changeset, _), do: changeset
end
diff --git a/core/systems/consent/revision_form.ex b/core/systems/consent/revision_form.ex
index 419359bb3..cad4c5035 100644
--- a/core/systems/consent/revision_form.ex
+++ b/core/systems/consent/revision_form.ex
@@ -48,12 +48,23 @@ defmodule Systems.Consent.RevisionForm do
|> assign(entity: entity)
|> flash_persister_saved()
- {:error, _} ->
- socket
- |> flash_persister_error(dgettext("eyra-consent", "consent-out-of-sync-error"))
+ {:error, changeset} ->
+ socket |> handle_save_errors(changeset)
end
end
+ defp handle_save_errors(socket, %{errors: errors}) do
+ handle_save_errors(socket, errors)
+ end
+
+ defp handle_save_errors(socket, [{_, {message, _}} | _]) do
+ socket |> flash_persister_error(message)
+ end
+
+ defp handle_save_errors(socket, _) do
+ socket |> flash_persister_error()
+ end
+
@impl true
def render(assigns) do
~H"""
diff --git a/core/test/systems/consent/_public_test.exs b/core/test/systems/consent/_public_test.exs
index 2426f3e92..b41ed419f 100644
--- a/core/test/systems/consent/_public_test.exs
+++ b/core/test/systems/consent/_public_test.exs
@@ -18,10 +18,10 @@ defmodule Systems.Consent.PublicTest do
Multi.new()
|> Multi.insert(:agreement, Consent.Public.prepare_agreement(Authorization.prepare_node()))
|> Multi.insert(:revision1, fn %{agreement: agreement} ->
- Consent.Public.prepare_revision("revision1", agreement)
+ Consent.Public.prepare_revision(agreement, "revision1")
end)
|> Multi.insert(:revision2, fn %{agreement: agreement} ->
- Consent.Public.prepare_revision("revision2", agreement)
+ Consent.Public.prepare_revision(agreement, "revision2")
end)
|> Repo.transaction()
@@ -48,7 +48,7 @@ defmodule Systems.Consent.PublicTest do
Multi.new()
|> Multi.insert(:agreement, Consent.Public.prepare_agreement(Authorization.prepare_node()))
|> Multi.insert(:revision1, fn %{agreement: agreement} ->
- Consent.Public.prepare_revision("revision1", agreement)
+ Consent.Public.prepare_revision(agreement, "revision1")
end)
|> Multi.insert(:signatureA1, fn %{revision1: revision1} ->
Consent.Public.prepare_signature(revision1, user_a)
@@ -57,7 +57,7 @@ defmodule Systems.Consent.PublicTest do
Consent.Public.prepare_signature(revision1, user_b)
end)
|> Multi.insert(:revision2, fn %{agreement: agreement} ->
- Consent.Public.prepare_revision("revision2", agreement)
+ Consent.Public.prepare_revision(agreement, "revision2")
end)
|> Multi.insert(:signatureA2, fn %{revision2: revision2} ->
Consent.Public.prepare_signature(revision2, user_a)
@@ -120,26 +120,25 @@ defmodule Systems.Consent.PublicTest do
assert Consent.Public.latest_unlocked_revision(agreement, [:agreement]) == nil
end
- test "latest_unlocked_revision_safe/2 returns new revision in empty agreement" do
+ test "bump_revision_if_needed!/1 returns first revision" do
agreement = Factories.insert!(:consent_agreement)
assert %Systems.Consent.RevisionModel{
- source: "Beschrijf hier de consent voorwaarden
",
- signatures: []
- } = Consent.Public.latest_unlocked_revision_safe(agreement, [:signatures])
+ source: "Beschrijf hier de consent voorwaarden
"
+ } = Consent.Public.bump_revision_if_needed!(agreement)
end
- test "latest_unlocked_revision_safe/2 returns latest revision" do
+ test "bump_revision_if_needed!/1 returns latest revision" do
agreement = Factories.insert!(:consent_agreement)
- _revision = Factories.insert!(:consent_revision, %{agreement: agreement, source: "source"})
+ %{id: id} = Factories.insert!(:consent_revision, %{agreement: agreement, source: "source"})
assert %Systems.Consent.RevisionModel{
- source: "source",
- signatures: []
- } = Consent.Public.latest_unlocked_revision_safe(agreement, [:signatures])
+ id: ^id,
+ source: "source"
+ } = Consent.Public.bump_revision_if_needed!(agreement)
end
- test "latest_unlocked_revision_safe/2 returns new revision on top of locked revision" do
+ test "bump_revision_if_needed!/1 returns new revision on top of locked revision" do
user = Factories.insert!(:member)
agreement = Factories.insert!(:consent_agreement)
@@ -148,9 +147,8 @@ defmodule Systems.Consent.PublicTest do
assert %Systems.Consent.RevisionModel{
id: revision_2_id,
- source: "source",
- signatures: []
- } = Consent.Public.latest_unlocked_revision_safe(agreement, [:signatures])
+ source: "source"
+ } = Consent.Public.bump_revision_if_needed!(agreement)
assert revision_1.id != revision_2_id
end