From 7813738f9131e2b392c7e8d73a9c72559713bb28 Mon Sep 17 00:00:00 2001 From: shibao Date: Tue, 20 Dec 2022 19:15:08 -0500 Subject: [PATCH] add json data export --- changelog.md | 1 + lib/memex/accounts/user.ex | 10 ++ lib/memex/contexts/context.ex | 9 ++ lib/memex/notes/note.ex | 9 ++ lib/memex/pipelines/pipeline.ex | 10 ++ lib/memex/pipelines/step.ex | 8 ++ .../controllers/export_controller.ex | 17 +++ lib/memex_web/router.ex | 1 + .../templates/user_settings/edit.html.heex | 26 +++-- priv/gettext/actions.pot | 7 +- priv/gettext/de/LC_MESSAGES/actions.po | 7 +- priv/gettext/de/LC_MESSAGES/errors.po | 26 ++--- priv/gettext/de/LC_MESSAGES/prompts.po | 2 +- priv/gettext/errors.pot | 26 ++--- priv/gettext/prompts.pot | 2 +- .../controllers/export_controller_test.exs | 101 ++++++++++++++++++ 16 files changed, 224 insertions(+), 38 deletions(-) create mode 100644 lib/memex_web/controllers/export_controller.ex create mode 100644 test/memex_web/controllers/export_controller_test.exs diff --git a/changelog.md b/changelog.md index 57f8d1e..e8bb92f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,6 @@ # v0.1.6 - fix formatting in note/context/step contents +- add json export for data # v0.1.5 - fix overflow on note/contexts/step contents diff --git a/lib/memex/accounts/user.ex b/lib/memex/accounts/user.ex index 411a5a3..48ce284 100644 --- a/lib/memex/accounts/user.ex +++ b/lib/memex/accounts/user.ex @@ -9,6 +9,16 @@ defmodule Memex.Accounts.User do alias Ecto.{Changeset, UUID} alias Memex.Invites.Invite + @derive {Jason.Encoder, + only: [ + :id, + :email, + :confirmed_at, + :role, + :locale, + :inserted_at, + :updated_at + ]} @derive {Inspect, except: [:password]} @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id diff --git a/lib/memex/contexts/context.ex b/lib/memex/contexts/context.ex index 9279adc..d4aba9b 100644 --- a/lib/memex/contexts/context.ex +++ b/lib/memex/contexts/context.ex @@ -9,6 +9,15 @@ defmodule Memex.Contexts.Context do alias Ecto.{Changeset, UUID} alias Memex.{Accounts.User, Repo} + @derive {Jason.Encoder, + only: [ + :slug, + :content, + :tags, + :visibility, + :inserted_at, + :updated_at + ]} @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "contexts" do diff --git a/lib/memex/notes/note.ex b/lib/memex/notes/note.ex index 89ee8ce..ec3aed7 100644 --- a/lib/memex/notes/note.ex +++ b/lib/memex/notes/note.ex @@ -8,6 +8,15 @@ defmodule Memex.Notes.Note do alias Ecto.{Changeset, UUID} alias Memex.{Accounts.User, Repo} + @derive {Jason.Encoder, + only: [ + :slug, + :content, + :tags, + :visibility, + :inserted_at, + :updated_at + ]} @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "notes" do diff --git a/lib/memex/pipelines/pipeline.ex b/lib/memex/pipelines/pipeline.ex index adca293..8d5a1af 100644 --- a/lib/memex/pipelines/pipeline.ex +++ b/lib/memex/pipelines/pipeline.ex @@ -8,6 +8,16 @@ defmodule Memex.Pipelines.Pipeline do alias Ecto.{Changeset, UUID} alias Memex.{Accounts.User, Pipelines.Steps.Step, Repo} + @derive {Jason.Encoder, + only: [ + :slug, + :description, + :tags, + :visibility, + :inserted_at, + :steps, + :updated_at + ]} @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "pipelines" do diff --git a/lib/memex/pipelines/step.ex b/lib/memex/pipelines/step.ex index 18727de..99f2c83 100644 --- a/lib/memex/pipelines/step.ex +++ b/lib/memex/pipelines/step.ex @@ -7,6 +7,14 @@ defmodule Memex.Pipelines.Steps.Step do alias Ecto.{Changeset, UUID} alias Memex.{Accounts.User, Pipelines.Pipeline} + @derive {Jason.Encoder, + only: [ + :title, + :content, + :position, + :inserted_at, + :updated_at + ]} @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "steps" do diff --git a/lib/memex_web/controllers/export_controller.ex b/lib/memex_web/controllers/export_controller.ex new file mode 100644 index 0000000..2c5d035 --- /dev/null +++ b/lib/memex_web/controllers/export_controller.ex @@ -0,0 +1,17 @@ +defmodule MemexWeb.ExportController do + use MemexWeb, :controller + alias Memex.{Contexts, Notes, Pipelines, Pipelines.Steps} + + def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do + pipelines = + Pipelines.list_pipelines(current_user) + |> Enum.map(fn pipeline -> Steps.preload_steps(pipeline, current_user) end) + + json(conn, %{ + user: current_user, + notes: Notes.list_notes(current_user), + contexts: Contexts.list_contexts(current_user), + pipelines: pipelines + }) + end +end diff --git a/lib/memex_web/router.ex b/lib/memex_web/router.ex index f20f3c5..8089434 100644 --- a/lib/memex_web/router.ex +++ b/lib/memex_web/router.ex @@ -76,6 +76,7 @@ defmodule MemexWeb.Router do put "/users/settings", UserSettingsController, :update delete "/users/settings/:id", UserSettingsController, :delete get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email + get "/export/:mode", ExportController, :export end scope "/", MemexWeb do diff --git a/lib/memex_web/templates/user_settings/edit.html.heex b/lib/memex_web/templates/user_settings/edit.html.heex index ce39cf1..ec079fd 100644 --- a/lib/memex_web/templates/user_settings/edit.html.heex +++ b/lib/memex_web/templates/user_settings/edit.html.heex @@ -136,12 +136,22 @@
- <.link - href={Routes.user_settings_path(@conn, :delete, @current_user)} - method={:delete} - class="btn btn-alert" - data-confirm={dgettext("prompts", "are you sure you want to delete your account?")} - > - <%= dgettext("actions", "delete user") %> - +
+ <.link + href={Routes.export_path(@conn, :export, :json)} + class="mx-4 my-2 btn btn-primary" + target="_blank" + > + <%= dgettext("actions", "export data as json") %> + + + <.link + href={Routes.user_settings_path(@conn, :delete, @current_user)} + method={:delete} + class="mx-4 my-2 btn btn-alert" + data-confirm={dgettext("prompts", "are you sure you want to delete your account?")} + > + <%= dgettext("actions", "delete user") %> + +
diff --git a/priv/gettext/actions.pot b/priv/gettext/actions.pot index ac3fdf3..4814d0d 100644 --- a/priv/gettext/actions.pot +++ b/priv/gettext/actions.pot @@ -66,7 +66,7 @@ msgstr "" msgid "delete" msgstr "" -#: lib/memex_web/templates/user_settings/edit.html.heex:145 +#: lib/memex_web/templates/user_settings/edit.html.heex:154 #, elixir-autogen, elixir-format msgid "delete user" msgstr "" @@ -155,3 +155,8 @@ msgstr "" #, elixir-autogen, elixir-format msgid "send instructions to reset password" msgstr "" + +#: lib/memex_web/templates/user_settings/edit.html.heex:145 +#, elixir-autogen, elixir-format +msgid "export data as json" +msgstr "" diff --git a/priv/gettext/de/LC_MESSAGES/actions.po b/priv/gettext/de/LC_MESSAGES/actions.po index 3b2c9e8..2cdf02f 100644 --- a/priv/gettext/de/LC_MESSAGES/actions.po +++ b/priv/gettext/de/LC_MESSAGES/actions.po @@ -67,7 +67,7 @@ msgstr "" msgid "delete" msgstr "" -#: lib/memex_web/templates/user_settings/edit.html.heex:145 +#: lib/memex_web/templates/user_settings/edit.html.heex:154 #, elixir-autogen, elixir-format msgid "delete user" msgstr "" @@ -156,3 +156,8 @@ msgstr "" #, elixir-autogen, elixir-format, fuzzy msgid "send instructions to reset password" msgstr "" + +#: lib/memex_web/templates/user_settings/edit.html.heex:145 +#, elixir-autogen, elixir-format +msgid "export data as json" +msgstr "" diff --git a/priv/gettext/de/LC_MESSAGES/errors.po b/priv/gettext/de/LC_MESSAGES/errors.po index b16e330..3a1bd21 100644 --- a/priv/gettext/de/LC_MESSAGES/errors.po +++ b/priv/gettext/de/LC_MESSAGES/errors.po @@ -76,22 +76,22 @@ msgstr "" msgid "You must confirm your account and log in to access this page." msgstr "" -#: lib/memex/accounts/user.ex:129 +#: lib/memex/accounts/user.ex:139 #, elixir-autogen, elixir-format msgid "did not change" msgstr "" -#: lib/memex/accounts/user.ex:150 +#: lib/memex/accounts/user.ex:160 #, elixir-autogen, elixir-format msgid "does not match password" msgstr "" -#: lib/memex/accounts/user.ex:187 +#: lib/memex/accounts/user.ex:197 #, elixir-autogen, elixir-format msgid "is not valid" msgstr "" -#: lib/memex/accounts/user.ex:85 +#: lib/memex/accounts/user.ex:95 #, elixir-autogen, elixir-format msgid "must have the @ sign and no spaces" msgstr "" @@ -102,12 +102,12 @@ msgstr "" msgid "oops, something went wrong! Please check the errors below" msgstr "" -#: lib/memex/contexts/context.ex:49 -#: lib/memex/contexts/context.ex:62 -#: lib/memex/notes/note.ex:48 -#: lib/memex/notes/note.ex:61 -#: lib/memex/pipelines/pipeline.ex:50 -#: lib/memex/pipelines/pipeline.ex:63 +#: lib/memex/contexts/context.ex:58 +#: lib/memex/contexts/context.ex:71 +#: lib/memex/notes/note.ex:57 +#: lib/memex/notes/note.ex:70 +#: lib/memex/pipelines/pipeline.ex:60 +#: lib/memex/pipelines/pipeline.ex:73 #, elixir-autogen, elixir-format msgid "invalid format: only numbers, letters and hyphen are accepted" msgstr "" @@ -132,9 +132,9 @@ msgstr "" msgid "unauthorized" msgstr "" -#: lib/memex/contexts/context.ex:75 -#: lib/memex/notes/note.ex:74 -#: lib/memex/pipelines/pipeline.ex:76 +#: lib/memex/contexts/context.ex:84 +#: lib/memex/notes/note.ex:83 +#: lib/memex/pipelines/pipeline.ex:86 #, elixir-autogen, elixir-format, fuzzy msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited" msgstr "" diff --git a/priv/gettext/de/LC_MESSAGES/prompts.po b/priv/gettext/de/LC_MESSAGES/prompts.po index 5428234..814fdb4 100644 --- a/priv/gettext/de/LC_MESSAGES/prompts.po +++ b/priv/gettext/de/LC_MESSAGES/prompts.po @@ -122,7 +122,7 @@ msgstr "" msgid "are you sure you want to delete the invite for %{invite_name}?" msgstr "" -#: lib/memex_web/templates/user_settings/edit.html.heex:143 +#: lib/memex_web/templates/user_settings/edit.html.heex:152 #, elixir-autogen, elixir-format msgid "are you sure you want to delete your account?" msgstr "" diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index 308c766..4b7d8cf 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -75,22 +75,22 @@ msgstr "" msgid "You must confirm your account and log in to access this page." msgstr "" -#: lib/memex/accounts/user.ex:129 +#: lib/memex/accounts/user.ex:139 #, elixir-autogen, elixir-format msgid "did not change" msgstr "" -#: lib/memex/accounts/user.ex:150 +#: lib/memex/accounts/user.ex:160 #, elixir-autogen, elixir-format msgid "does not match password" msgstr "" -#: lib/memex/accounts/user.ex:187 +#: lib/memex/accounts/user.ex:197 #, elixir-autogen, elixir-format msgid "is not valid" msgstr "" -#: lib/memex/accounts/user.ex:85 +#: lib/memex/accounts/user.ex:95 #, elixir-autogen, elixir-format msgid "must have the @ sign and no spaces" msgstr "" @@ -101,12 +101,12 @@ msgstr "" msgid "oops, something went wrong! Please check the errors below" msgstr "" -#: lib/memex/contexts/context.ex:49 -#: lib/memex/contexts/context.ex:62 -#: lib/memex/notes/note.ex:48 -#: lib/memex/notes/note.ex:61 -#: lib/memex/pipelines/pipeline.ex:50 -#: lib/memex/pipelines/pipeline.ex:63 +#: lib/memex/contexts/context.ex:58 +#: lib/memex/contexts/context.ex:71 +#: lib/memex/notes/note.ex:57 +#: lib/memex/notes/note.ex:70 +#: lib/memex/pipelines/pipeline.ex:60 +#: lib/memex/pipelines/pipeline.ex:73 #, elixir-autogen, elixir-format msgid "invalid format: only numbers, letters and hyphen are accepted" msgstr "" @@ -131,9 +131,9 @@ msgstr "" msgid "unauthorized" msgstr "" -#: lib/memex/contexts/context.ex:75 -#: lib/memex/notes/note.ex:74 -#: lib/memex/pipelines/pipeline.ex:76 +#: lib/memex/contexts/context.ex:84 +#: lib/memex/notes/note.ex:83 +#: lib/memex/pipelines/pipeline.ex:86 #, elixir-autogen, elixir-format msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited" msgstr "" diff --git a/priv/gettext/prompts.pot b/priv/gettext/prompts.pot index 89db7c2..bc0fd23 100644 --- a/priv/gettext/prompts.pot +++ b/priv/gettext/prompts.pot @@ -121,7 +121,7 @@ msgstr "" msgid "are you sure you want to delete the invite for %{invite_name}?" msgstr "" -#: lib/memex_web/templates/user_settings/edit.html.heex:143 +#: lib/memex_web/templates/user_settings/edit.html.heex:152 #, elixir-autogen, elixir-format msgid "are you sure you want to delete your account?" msgstr "" diff --git a/test/memex_web/controllers/export_controller_test.exs b/test/memex_web/controllers/export_controller_test.exs new file mode 100644 index 0000000..053af54 --- /dev/null +++ b/test/memex_web/controllers/export_controller_test.exs @@ -0,0 +1,101 @@ +defmodule MemexWeb.ExportControllerTest do + @moduledoc """ + Tests the export function + """ + + use MemexWeb.ConnCase + import Memex.{ContextsFixtures, NotesFixtures, PipelinesFixtures, StepsFixtures} + + @moduletag :export_controller_test + + setup %{conn: conn} do + current_user = user_fixture() |> confirm_user() + + [ + current_user: current_user, + conn: conn |> log_in_user(current_user) + ] + end + + defp add_data(%{current_user: current_user}) do + note = note_fixture(current_user) + context = context_fixture(current_user) + pipeline = pipeline_fixture(current_user) + step = step_fixture(0, pipeline, current_user) + + %{ + note: note, + context: context, + pipeline: pipeline, + step: step + } + end + + describe "Exports data" do + setup [:add_data] + + test "in JSON", %{ + conn: conn, + current_user: current_user, + note: note, + context: context, + pipeline: pipeline, + step: step + } do + conn = get(conn, Routes.export_path(conn, :export, :json)) + + ideal_note = %{ + "slug" => note.slug, + "content" => note.content, + "tags" => note.tags, + "visibility" => note.visibility |> to_string(), + "inserted_at" => note.inserted_at |> NaiveDateTime.to_iso8601(), + "updated_at" => note.updated_at |> NaiveDateTime.to_iso8601() + } + + ideal_context = %{ + "slug" => context.slug, + "content" => context.content, + "tags" => context.tags, + "visibility" => context.visibility |> to_string(), + "inserted_at" => context.inserted_at |> NaiveDateTime.to_iso8601(), + "updated_at" => context.updated_at |> NaiveDateTime.to_iso8601() + } + + ideal_pipeline = %{ + "slug" => pipeline.slug, + "description" => pipeline.description, + "tags" => pipeline.tags, + "visibility" => pipeline.visibility |> to_string(), + "inserted_at" => pipeline.inserted_at |> NaiveDateTime.to_iso8601(), + "updated_at" => pipeline.updated_at |> NaiveDateTime.to_iso8601(), + "steps" => [ + %{ + "title" => step.title, + "content" => step.content, + "position" => step.position, + "inserted_at" => step.inserted_at |> NaiveDateTime.to_iso8601(), + "updated_at" => step.updated_at |> NaiveDateTime.to_iso8601() + } + ] + } + + ideal_user = %{ + "confirmed_at" => + current_user.confirmed_at |> Jason.encode!() |> String.replace(~r/\"/, ""), + "email" => current_user.email, + "id" => current_user.id, + "locale" => current_user.locale, + "role" => to_string(current_user.role), + "inserted_at" => current_user.inserted_at |> NaiveDateTime.to_iso8601(), + "updated_at" => current_user.updated_at |> NaiveDateTime.to_iso8601() + } + + json_resp = conn |> json_response(200) + assert %{"notes" => [^ideal_note]} = json_resp + assert %{"contexts" => [^ideal_context]} = json_resp + assert %{"pipelines" => [^ideal_pipeline]} = json_resp + assert %{"user" => ^ideal_user} = json_resp + end + end +end