diff --git a/config/config.exs b/config/config.exs index bf7e7db44..3ba3d47b9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -260,7 +260,8 @@ config :pleroma, :instance, password_reset_token_validity: 60 * 60 * 24, profile_directory: true, privileged_staff: false, - local_bubble: [] + local_bubble: [], + max_frontend_settings_json_chars: 100_000 config :pleroma, :welcome, direct_message: [ diff --git a/lib/pleroma/akkoma/frontend_setting_profile.ex b/lib/pleroma/akkoma/frontend_setting_profile.ex new file mode 100644 index 000000000..1d11b1c8b --- /dev/null +++ b/lib/pleroma/akkoma/frontend_setting_profile.ex @@ -0,0 +1,76 @@ +defmodule Pleroma.Akkoma.FrontendSettingProfile do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + alias Pleroma.Repo + alias Pleroma.Config + alias Pleroma.User + + @primary_key false + schema "user_frontend_setting_profiles" do + belongs_to(:user, Pleroma.User, primary_key: true, type: FlakeId.Ecto.CompatType) + field(:frontend_name, :string, primary_key: true) + field(:profile_name, :string, primary_key: true) + field(:settings, :map) + timestamps() + end + + def changeset(%__MODULE__{} = struct, attrs) do + struct + |> cast(attrs, [:user_id, :frontend_name, :profile_name, :settings]) + |> validate_required([:user_id, :frontend_name, :profile_name, :settings]) + |> validate_length(:frontend_name, min: 1, max: 255) + |> validate_length(:profile_name, min: 1, max: 255) + |> validate_settings_length(Config.get([:instance, :max_frontend_settings_json_chars])) + end + + def create_or_update(%User{} = user, frontend_name, profile_name, settings) do + struct = + case get_by_user_and_frontend_name_and_profile_name(user.id, frontend_name, profile_name) do + nil -> + %__MODULE__{} + + %__MODULE__{} = profile -> + profile + end + + struct + |> changeset(%{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings + }) + |> Repo.insert_or_update() + end + + def get_all_by_user_and_frontend_name(user_id, frontend_name) do + Repo.all( + from(p in __MODULE__, where: p.user_id == ^user_id and p.frontend_name == ^frontend_name) + ) + end + + def get_by_user_and_frontend_name_and_profile_name(user_id, frontend_name, profile_name) do + Repo.one( + from(p in __MODULE__, + where: + p.user_id == ^user_id and p.frontend_name == ^frontend_name and + p.profile_name == ^profile_name + ) + ) + end + + defp validate_settings_length( + %Ecto.Changeset{changes: %{settings: settings}} = changeset, + max_length + ) do + settings_json = Jason.encode!(settings) + + if String.length(settings_json) > max_length do + add_error(changeset, :settings, "is too long") + else + changeset + end + end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a36c1c330..3c417000c 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -165,6 +165,8 @@ defmodule Pleroma.User do has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id) has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id) + has_many(:frontend_profiles, Pleroma.Akkoma.FrontendSettingProfile) + for {relationship_type, [ {outgoing_relation, outgoing_relation_target}, diff --git a/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs b/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs new file mode 100644 index 000000000..9e66ad0fd --- /dev/null +++ b/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs @@ -0,0 +1,30 @@ +defmodule Pleroma.Repo.Migrations.AddUserFrontendProfiles do + use Ecto.Migration + + def up do + create_if_not_exists table("user_frontend_setting_profiles", primary_key: false) do + add(:user_id, references(:users, type: :uuid, on_delete: :delete_all), primary_key: true) + add(:frontend_name, :string, primary_key: true) + add(:profile_name, :string, primary_key: true) + add(:settings, :map) + timestamps() + end + + create_if_not_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name]) + ) + + create_if_not_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name]) + ) + end + + def down do + drop_if_exists(table("user_frontend_setting_profiles")) + drop_if_exists(unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name])) + + drop_if_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name]) + ) + end +end diff --git a/test/pleroma/akkoma/frontend_setting_profile_test.exs b/test/pleroma/akkoma/frontend_setting_profile_test.exs new file mode 100644 index 000000000..c62a5367d --- /dev/null +++ b/test/pleroma/akkoma/frontend_setting_profile_test.exs @@ -0,0 +1,111 @@ +defmodule Pleroma.Akkoma.FrontendSettingProfileTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Akkoma.FrontendSettingProfile + + import Pleroma.Factory + + describe "changeset/2" do + test "valid" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings + } + + assert %{valid?: true} = FrontendSettingProfile.changeset(struct, attrs) + end + + test "when settings is too long" do + clear_config([:instance, :max_frontend_settings_json_chars], 10) + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"verylong" => "verylongoops"} + struct = %FrontendSettingProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings + } + + assert %{valid?: false, errors: [settings: {"is too long", _}]} = + FrontendSettingProfile.changeset(struct, attrs) + end + + test "when frontend name is too short" do + user = insert(:user) + frontend_name = "" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings + } + + assert %{valid?: false, errors: [frontend_name: {"can't be blank", _}]} = + FrontendSettingProfile.changeset(struct, attrs) + end + + test "when profile name is too short" do + user = insert(:user) + frontend_name = "test" + profile_name = "" + settings = %{"test" => "test"} + struct = %FrontendSettingProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings + } + + assert %{valid?: false, errors: [profile_name: {"can't be blank", _}]} = + FrontendSettingProfile.changeset(struct, attrs) + end + end + + describe "create_or_update/2" do + test "it should create a new record" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + + assert {:ok, %FrontendSettingProfile{}} = + FrontendSettingProfile.create_or_update(user, frontend_name, profile_name, settings) + end + + test "it should update a record" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: profile_name, + settings: %{"test" => "test"} + ) + + settings = %{"test" => "test2"} + + assert {:ok, %FrontendSettingProfile{settings: ^settings}} = + FrontendSettingProfile.create_or_update(user, frontend_name, profile_name, settings) + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index efcd8039e..691dca738 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -663,4 +663,14 @@ defmodule Pleroma.Factory do |> Map.merge(params) |> Pleroma.Announcement.add_rendered_properties() end + + def frontend_setting_profile_factory(params \\ %{}) do + %Pleroma.Akkoma.FrontendSettingProfile{ + user: build(:user), + frontend_name: "akkoma-fe", + profile_name: "default", + settings: %{"test" => "test"} + } + |> Map.merge(params) + end end