From 4d7f2296415032dd0ce2a51dc83f4aa7cba1eef0 Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 31 Jul 2024 10:37:35 -0400 Subject: [PATCH] wip --- lib/philomena/comments.ex | 49 ++++-- lib/philomena/comments/version.ex | 25 +++ lib/philomena/posts.ex | 46 +++-- lib/philomena/posts/version.ex | 24 +++ lib/philomena/versions.ex | 126 +++++++------- lib/philomena/versions/attribution.ex | 14 +- lib/philomena/versions/difference.ex | 8 + lib/philomena/versions/version.ex | 43 ----- .../image/comment/history_controller.ex | 19 +-- .../controllers/image/comment_controller.ex | 10 +- .../topic/post/history_controller.ex | 16 +- .../controllers/topic/post_controller.ex | 8 +- .../image/comment/history/index.html.slime | 47 +++--- .../topic/post/history/index.html.slime | 7 +- .../views/image/comment/history_view.ex | 7 + .../20240731132924_new_versions.exs | 72 ++++++++ priv/repo/structure.sql | 157 ++++++++++++++++++ 17 files changed, 474 insertions(+), 204 deletions(-) create mode 100644 lib/philomena/comments/version.ex create mode 100644 lib/philomena/posts/version.ex create mode 100644 lib/philomena/versions/difference.ex delete mode 100644 lib/philomena/versions/version.ex create mode 100644 priv/repo/migrations/20240731132924_new_versions.exs diff --git a/lib/philomena/comments.ex b/lib/philomena/comments.ex index 4498dca8..f6e8c500 100644 --- a/lib/philomena/comments.ex +++ b/lib/philomena/comments.ex @@ -10,13 +10,13 @@ defmodule Philomena.Comments do alias PhilomenaQuery.Search alias Philomena.UserStatistics alias Philomena.Comments.Comment + alias Philomena.Comments.Version alias Philomena.Comments.SearchIndex, as: CommentIndex alias Philomena.IndexWorker alias Philomena.Images.Image alias Philomena.Images alias Philomena.Notifications alias Philomena.NotificationWorker - alias Philomena.Versions alias Philomena.Reports @doc """ @@ -93,21 +93,46 @@ defmodule Philomena.Comments do """ def update_comment(%Comment{} = comment, editor, attrs) do - now = DateTime.utc_now(:second) - current_body = comment.body - current_reason = comment.edit_reason + version_changeset = + Version.changeset(%Version{}, comment, editor, %{ + body: comment.body, + edit_reason: comment.edit_reason, + created_at: comment.edited_at || comment.created_at + }) - comment_changes = Comment.changeset(comment, attrs, now) + now = DateTime.utc_now(:second) + comment_changeset = Comment.changeset(comment, attrs, now) Multi.new() - |> Multi.update(:comment, comment_changes) - |> Multi.run(:version, fn _repo, _changes -> - Versions.create_version("Comment", comment.id, editor.id, %{ - "body" => current_body, - "edit_reason" => current_reason - }) - end) + |> Multi.insert(:version, version_changeset) + |> Multi.update(:comment, comment_changeset) |> Repo.transaction() + |> case do + {:ok, %{comment: comment}} -> + if not comment.approved do + report_non_approved(comment) + end + + reindex_comment(comment) + + {:ok, comment} + + _ -> + {:error, comment_changeset} + end + end + + def list_comment_versions(%Comment{} = comment, collection_renderer, pagination) do + versions = + Version + |> where(comment_id: ^comment.id) + |> order_by(desc: :created_at, desc: :id) + |> select([v], %{v | index: over(row_number(), order_by: [asc: :created_at, asc: :id])}) + |> preload(:user) + |> Repo.paginate(pagination) + + bodies = collection_renderer.(versions) + put_in(versions.entries, Enum.zip(versions, bodies)) end @doc """ diff --git a/lib/philomena/comments/version.ex b/lib/philomena/comments/version.ex new file mode 100644 index 00000000..9963b05a --- /dev/null +++ b/lib/philomena/comments/version.ex @@ -0,0 +1,25 @@ +defmodule Philomena.Comments.Version do + use Ecto.Schema + import Ecto.Changeset + + alias Philomena.Comments.Comment + alias Philomena.Users.User + + schema "comment_versions" do + belongs_to :comment, Comment + belongs_to :user, User + field :body, :string + field :edit_reason, :string + field :index, :integer, virtual: true + timestamps(inserted_at: :created_at, updated_at: false, type: :utc_datetime) + end + + @doc false + def changeset(comment_version, comment, user, attrs) do + comment_version + |> cast(attrs, [:body, :edit_reason, :created_at]) + |> put_assoc(:comment, comment) + |> put_assoc(:user, user) + |> validate_required([:comment, :user, :body, :created_at]) + end +end diff --git a/lib/philomena/posts.ex b/lib/philomena/posts.ex index af29c2e3..aa28b27f 100644 --- a/lib/philomena/posts.ex +++ b/lib/philomena/posts.ex @@ -12,12 +12,12 @@ defmodule Philomena.Posts do alias Philomena.Topics alias Philomena.UserStatistics alias Philomena.Posts.Post + alias Philomena.Posts.Version alias Philomena.Posts.SearchIndex, as: PostIndex alias Philomena.IndexWorker alias Philomena.Forums.Forum alias Philomena.Notifications alias Philomena.NotificationWorker - alias Philomena.Versions alias Philomena.Reports @doc """ @@ -142,32 +142,46 @@ defmodule Philomena.Posts do """ def update_post(%Post{} = post, editor, attrs) do - now = DateTime.utc_now(:second) - current_body = post.body - current_reason = post.edit_reason + version_changeset = + Version.changeset(%Version{}, post, editor, %{ + body: post.body, + edit_reason: post.edit_reason, + created_at: post.edited_at || post.created_at + }) - post_changes = Post.changeset(post, attrs, now) + now = DateTime.utc_now(:second) + post_changeset = Post.changeset(post, attrs, now) Multi.new() - |> Multi.update(:post, post_changes) - |> Multi.run(:version, fn _repo, _changes -> - Versions.create_version("Post", post.id, editor.id, %{ - "body" => current_body, - "edit_reason" => current_reason - }) - end) + |> Multi.insert(:version, version_changeset) + |> Multi.update(:post, post_changeset) |> Repo.transaction() |> case do - {:ok, %{post: post}} = result -> + {:ok, %{post: post}} -> + if not post.approved do + report_non_approved(post) + end + reindex_post(post) - result + {:ok, post} - error -> - error + _ -> + {:error, post_changeset} end end + def list_post_versions(%Post{} = post, collection_renderer, pagination) do + versions = + Version + |> where(post_id: ^post.id) + |> order_by(desc: :created_at, desc: :id) + |> Repo.paginate(pagination) + + bodies = collection_renderer.(versions) + put_in(versions.entries, Enum.zip(versions, bodies)) + end + @doc """ Deletes a Post. diff --git a/lib/philomena/posts/version.ex b/lib/philomena/posts/version.ex new file mode 100644 index 00000000..c4d27190 --- /dev/null +++ b/lib/philomena/posts/version.ex @@ -0,0 +1,24 @@ +defmodule Philomena.Posts.Version do + use Ecto.Schema + import Ecto.Changeset + + alias Philomena.Posts.Post + alias Philomena.Users.User + + schema "post_versions" do + belongs_to :post, Post + belongs_to :user, User + field :body, :string + field :edit_reason, :string + timestamps(inserted_at: :created_at, updated_at: false, type: :utc_datetime) + end + + @doc false + def changeset(post_version, post, user, attrs) do + post_version + |> cast(attrs, [:body, :edit_reason, :created_at]) + |> put_assoc(:post, post) + |> put_assoc(:user, user) + |> validate_required([:post, :user, :body, :created_at]) + end +end diff --git a/lib/philomena/versions.ex b/lib/philomena/versions.ex index 44c5cf38..3af95c93 100644 --- a/lib/philomena/versions.ex +++ b/lib/philomena/versions.ex @@ -5,75 +5,77 @@ defmodule Philomena.Versions do import Ecto.Query, warn: false alias Philomena.Repo - - alias Philomena.Versions.Version - alias Philomena.Users.User - - def load_data_and_associations(versions, parent) do - user_ids = - versions - |> Enum.map(& &1.whodunnit) - |> Enum.reject(&is_nil/1) - - users = - User - |> where([u], u.id in ^user_ids) - |> preload(awards: :badge) - |> Repo.all() - |> Map.new(&{to_string(&1.id), &1}) - - {versions, _last_body} = - versions - |> Enum.map_reduce( - {parent.body, parent.edit_reason}, - fn version, {previous_body, previous_reason} -> - json = Jason.decode!(version.object || "{}") - body = json["body"] || "" - edit_reason = json["edit_reason"] - - v = %{ - version - | parent: parent, - user: users[version.whodunnit], - body: body, - edit_reason: previous_reason, - difference: difference(body, previous_body) - } - - {v, {body, edit_reason}} - end - ) - - versions - end - - defp difference(previous, nil), do: [eq: previous] - defp difference(previous, next), do: String.myers_difference(previous, next) + alias Philomena.Versions.Difference @doc """ - Creates a version. + Calculate a list of `m:Philomena.Versions.Difference` structs that represent + paginated differences between different versions of the object. + + When expanded, the list of differences may look like: + + [ + %Difference{ + previous_version: %CommentVersion{}, + parent: %Comment{}, + user: %User{}, + difference: [del: "goodbye ", ins: "hello ", eq: "world"], + } + ] ## Examples - iex> create_version(%{field: value}) - {:ok, %Version{}} - - iex> create_version(%{field: bad_value}) - {:error, %Ecto.Changeset{}} + iex> compute_text_differences(CommentVersion, %Comment{}, :body, page: 1, page_size: 25) + %Scrivener.Page{} """ - def create_version(item_type, item_id, whodunnit, attrs \\ %{}) do - %Version{ - item_type: item_type, - item_id: item_id, - event: "update", - whodunnit: whodunnit(whodunnit) - } - |> Version.changeset(attrs, item_id) - |> Repo.insert() + def compute_text_differences(query, parent, name, pagination) do + page = Repo.paginate(preload_and_order(query), pagination) + initial = get_comparison_version(query, parent, page) + + {differences, _prev} = + Enum.map_reduce(page, initial, fn older, newer -> + d = %Difference{ + previous_version: newer, + created_at: older.created_at, + parent: parent, + user: older.user, + difference: difference(older, newer, name) + } + + {d, older} + end) + + %{page | entries: differences} end - # revolver ocelot - defp whodunnit(user_id) when is_integer(user_id), do: to_string(user_id) - defp whodunnit(nil), do: nil + # + # Get the first version to use when reducing the list of differences. + # + defp get_comparison_version(query, parent, page) do + curr = Enum.at(page, 0) + + prev = + if curr do + query + |> where([v], v.created_at > ^curr.created_at and v.id > ^curr.id) + |> order_by(asc: :created_at, asc: :id) + |> limit(1) + |> Repo.one() + end + + prev || parent + end + + defp preload_and_order(query) do + query + |> preload(:user) + |> order_by(desc: :created_at, desc: :id) + end + + defp difference(curr, prev, name) do + curr_body = Map.fetch!(curr, name) + prev_body = Map.fetch!(prev, name) + + String.myers_difference(curr_body, prev_body) + end end diff --git a/lib/philomena/versions/attribution.ex b/lib/philomena/versions/attribution.ex index 55a05be7..81d10eb1 100644 --- a/lib/philomena/versions/attribution.ex +++ b/lib/philomena/versions/attribution.ex @@ -1,14 +1,14 @@ -defimpl Philomena.Attribution, for: Philomena.Versions.Version do - def object_identifier(version) do - Philomena.Attribution.object_identifier(version.parent) +defimpl Philomena.Attribution, for: Philomena.Versions.Difference do + def object_identifier(difference) do + Philomena.Attribution.object_identifier(difference.parent) end - def best_user_identifier(version) do - Philomena.Attribution.best_user_identifier(version.parent) + def best_user_identifier(difference) do + Philomena.Attribution.best_user_identifier(difference.parent) end - def anonymous?(version) do - same_user?(version.user, version.parent) and !!version.parent.anonymous + def anonymous?(difference) do + same_user?(difference.user, difference.parent) and !!difference.parent.anonymous end defp same_user?(%{id: id}, %{user_id: id}), do: true diff --git a/lib/philomena/versions/difference.ex b/lib/philomena/versions/difference.ex new file mode 100644 index 00000000..ff571250 --- /dev/null +++ b/lib/philomena/versions/difference.ex @@ -0,0 +1,8 @@ +defmodule Philomena.Versions.Difference do + defstruct previous_version: nil, + created_at: nil, + parent: nil, + user: nil, + edit_reason: nil, + difference: [] +end diff --git a/lib/philomena/versions/version.ex b/lib/philomena/versions/version.ex deleted file mode 100644 index 5c8baf49..00000000 --- a/lib/philomena/versions/version.ex +++ /dev/null @@ -1,43 +0,0 @@ -defmodule Philomena.Versions.Version do - use Ecto.Schema - import Ecto.Changeset - - schema "versions" do - field :event, :string - field :whodunnit, :string - field :object, :string - - # fixme: rails polymorphic relation - field :item_id, :integer - field :item_type, :string - - field :user, :any, virtual: true - field :parent, :any, virtual: true - field :body, :string, virtual: true - field :edit_reason, :string, virtual: true - field :difference, :any, virtual: true - - timestamps(inserted_at: :created_at, updated_at: false, type: :utc_datetime) - end - - @doc false - def changeset(version, attrs, item_id) do - version - |> cast(attrs, [:body, :edit_reason]) - |> put_object(item_id) - end - - defp put_object(changeset, item_id) do - body = get_field(changeset, :body) - edit_reason = get_field(changeset, :edit_reason) - - object = - Jason.encode!(%{ - id: item_id, - body: body, - edit_reason: edit_reason - }) - - change(changeset, object: object) - end -end diff --git a/lib/philomena_web/controllers/image/comment/history_controller.ex b/lib/philomena_web/controllers/image/comment/history_controller.ex index d7ad3792..8ea248f9 100644 --- a/lib/philomena_web/controllers/image/comment/history_controller.ex +++ b/lib/philomena_web/controllers/image/comment/history_controller.ex @@ -1,11 +1,9 @@ defmodule PhilomenaWeb.Image.Comment.HistoryController do use PhilomenaWeb, :controller - alias Philomena.Versions.Version - alias Philomena.Versions + alias PhilomenaWeb.MarkdownRenderer alias Philomena.Images.Image - alias Philomena.Repo - import Ecto.Query + alias Philomena.Comments plug PhilomenaWeb.CanaryMapPlug, index: :show plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true @@ -15,18 +13,13 @@ defmodule PhilomenaWeb.Image.Comment.HistoryController do def index(conn, _params) do image = conn.assigns.image comment = conn.assigns.comment - - versions = - Version - |> where(item_type: "Comment", item_id: ^comment.id) - |> order_by(desc: :created_at) - |> limit(25) - |> Repo.all() - |> Versions.load_data_and_associations(comment) + renderer = &MarkdownRenderer.render_collection(&1, conn) + versions = Comments.list_comment_versions(comment, renderer, conn.assigns.scrivener) render(conn, "index.html", title: "Comment History for comment #{comment.id} on image #{image.id}", - versions: versions + versions: versions, + body: renderer.([comment]) ) end end diff --git a/lib/philomena_web/controllers/image/comment_controller.ex b/lib/philomena_web/controllers/image/comment_controller.ex index ca0ff9ad..befe6666 100644 --- a/lib/philomena_web/controllers/image/comment_controller.ex +++ b/lib/philomena_web/controllers/image/comment_controller.ex @@ -111,24 +111,18 @@ defmodule PhilomenaWeb.Image.CommentController do def update(conn, %{"comment" => comment_params}) do case Comments.update_comment(conn.assigns.comment, conn.assigns.current_user, comment_params) do - {:ok, %{comment: comment}} -> - if not comment.approved do - Comments.report_non_approved(comment) - end - + {:ok, comment} -> PhilomenaWeb.Endpoint.broadcast!( "firehose", "comment:update", PhilomenaWeb.Api.Json.CommentView.render("show.json", %{comment: comment}) ) - Comments.reindex_comment(comment) - conn |> put_flash(:info, "Comment updated successfully.") |> redirect(to: ~p"/images/#{conn.assigns.image}" <> "#comment_#{comment.id}") - {:error, :comment, changeset, _changes} -> + {:error, changeset} -> render(conn, "edit.html", comment: conn.assigns.comment, changeset: changeset) end end diff --git a/lib/philomena_web/controllers/topic/post/history_controller.ex b/lib/philomena_web/controllers/topic/post/history_controller.ex index d06480e7..af7b205b 100644 --- a/lib/philomena_web/controllers/topic/post/history_controller.ex +++ b/lib/philomena_web/controllers/topic/post/history_controller.ex @@ -1,11 +1,9 @@ defmodule PhilomenaWeb.Topic.Post.HistoryController do use PhilomenaWeb, :controller - alias Philomena.Versions.Version - alias Philomena.Versions + alias PhilomenaWeb.MarkdownRenderer alias Philomena.Forums.Forum - alias Philomena.Repo - import Ecto.Query + alias Philomena.Posts plug PhilomenaWeb.CanaryMapPlug, index: :show @@ -21,14 +19,8 @@ defmodule PhilomenaWeb.Topic.Post.HistoryController do def index(conn, _params) do topic = conn.assigns.topic post = conn.assigns.post - - versions = - Version - |> where(item_type: "Post", item_id: ^post.id) - |> order_by(desc: :created_at) - |> limit(25) - |> Repo.all() - |> Versions.load_data_and_associations(post) + renderer = &MarkdownRenderer.render_collection(&1, conn) + versions = Posts.list_post_versions(post, renderer, conn.assigns.scrivener) render(conn, "index.html", title: "Post History for Post #{post.id} - #{topic.title} - Forums", diff --git a/lib/philomena_web/controllers/topic/post_controller.ex b/lib/philomena_web/controllers/topic/post_controller.ex index 8f90abd0..1546061e 100644 --- a/lib/philomena_web/controllers/topic/post_controller.ex +++ b/lib/philomena_web/controllers/topic/post_controller.ex @@ -79,11 +79,7 @@ defmodule PhilomenaWeb.Topic.PostController do user = conn.assigns.current_user case Posts.update_post(post, user, post_params) do - {:ok, %{post: post}} -> - if not post.approved do - Posts.report_non_approved(post) - end - + {:ok, post} -> conn |> put_flash(:info, "Post successfully edited.") |> redirect( @@ -92,7 +88,7 @@ defmodule PhilomenaWeb.Topic.PostController do "#post_#{post.id}" ) - {:error, :post, changeset, _changes} -> + {:error, changeset} -> render(conn, "edit.html", post: conn.assigns.post, changeset: changeset) end end diff --git a/lib/philomena_web/templates/image/comment/history/index.html.slime b/lib/philomena_web/templates/image/comment/history/index.html.slime index bb27e827..14363026 100644 --- a/lib/philomena_web/templates/image/comment/history/index.html.slime +++ b/lib/philomena_web/templates/image/comment/history/index.html.slime @@ -1,46 +1,49 @@ h1 - ' Viewing last 25 versions of comment by + ' Viewing history for comment by = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @comment, conn: @conn ' on image a href=~p"/images/#{@comment.image}" | # = @comment.image_id -= for version <- @versions do +h2 Current version += render PhilomenaWeb.CommentView, "_comment.html", comment: @comment, body: @body, conn: @conn + +h2 Previous versions += for {version, body} <- @versions do + - comment = merge_version(@comment, version) + article.block.communication .block__content.flex.flex--no-wrap .flex__fixed.spacing-right - = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @comment, conn: @conn + = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: comment, conn: @conn .flex__grow.communication__body - span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @comment, awards: true, conn: @conn + span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: comment, awards: true, conn: @conn br - = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @comment, conn: @conn + = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: comment, conn: @conn .communication__body__text - = for edit <- version.difference do - = case edit do - - {:eq, value} -> - = escape_nl2br(value) - - - {:ins, value} -> - ins.differ = escape_nl2br(value) - - - {:del, value} -> - del.differ = escape_nl2br(value) + = body .block__content.communication__options .flex.flex--wrap.flex--spaced-out div - = if version.edit_reason not in [nil, ""] do - ' Reason: - = version.edit_reason - - else - ' No reason given + = cond do + - version.index > 1 and comment.edit_reason not in [nil, ""] -> + ' Reason: + = comment.edit_reason + - version.index > 1 -> + ' No reason given + - true -> + ' Original .flex__right - ' Edited + = if version.index == 1 do + ' Created + - else + ' Edited => pretty_time(version.created_at) ' by - => render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: version, conn: @conn + => render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: comment, conn: @conn diff --git a/lib/philomena_web/templates/topic/post/history/index.html.slime b/lib/philomena_web/templates/topic/post/history/index.html.slime index e455ff57..efab6150 100644 --- a/lib/philomena_web/templates/topic/post/history/index.html.slime +++ b/lib/philomena_web/templates/topic/post/history/index.html.slime @@ -5,7 +5,7 @@ h1 a href=(~p"/forums/#{@post.topic.forum}/topics/#{@post.topic}?#{[post_id: @post.id]}" <> "#post_#{@post.id}") = @post.topic.title -= for version <- @versions do +/= for version <- @versions do article.block.communication .block__content.flex.flex--no-wrap .flex__fixed.spacing-right @@ -31,10 +31,11 @@ h1 .block__content.communication__options .flex.flex--wrap.flex--spaced-out + - edit_reason = version.previous_version.edit_reason div - = if version.edit_reason not in [nil, ""] do + = if edit_reason not in [nil, ""] do ' Reason: - = version.edit_reason + = edit_reason - else ' No reason given diff --git a/lib/philomena_web/views/image/comment/history_view.ex b/lib/philomena_web/views/image/comment/history_view.ex index 3d2423c0..3289d206 100644 --- a/lib/philomena_web/views/image/comment/history_view.ex +++ b/lib/philomena_web/views/image/comment/history_view.ex @@ -1,3 +1,10 @@ defmodule PhilomenaWeb.Image.Comment.HistoryView do use PhilomenaWeb, :view + + def merge_version(comment, version) do + comment + |> Map.put(:body, version.body) + |> Map.put(:edited_at, version.created_at) + |> Map.put(:edit_reason, version.edit_reason) + end end diff --git a/priv/repo/migrations/20240731132924_new_versions.exs b/priv/repo/migrations/20240731132924_new_versions.exs new file mode 100644 index 00000000..4d1663d9 --- /dev/null +++ b/priv/repo/migrations/20240731132924_new_versions.exs @@ -0,0 +1,72 @@ +defmodule Philomena.Repo.Migrations.NewVersions do + use Ecto.Migration + + def up do + create table(:comment_versions) do + add :comment_id, references(:comments, on_delete: :delete_all), null: false + add :user_id, references(:users, on_delete: :delete_all), null: false + timestamps inserted_at: :created_at, updated_at: false + + add :body, :string, null: false + add :edit_reason, :string + end + + create table(:post_versions) do + add :post_id, references(:posts, on_delete: :delete_all), null: false + add :user_id, references(:users, on_delete: :delete_all), null: false + timestamps inserted_at: :created_at, updated_at: false + + add :body, :string, null: false + add :edit_reason, :string + end + + create index(:comment_versions, [:comment_id, "created_at desc"]) + create index(:comment_versions, [:user_id]) + create index(:post_versions, [:post_id, "created_at desc"]) + create index(:post_versions, [:user_id]) + + insert_statements = + """ + insert into comment_versions (comment_id, user_id, created_at, body, edit_reason) + select + v.item_id as comment_id, + v.whodunnit::bigint as user_id, + v.created_at, + v.object::json->>'body' as body, + v.object::json->>'edit_reason' as edit_reason + from versions v + where v.item_type = 'Comment' + and exists(select 1 from comments c where c.id = v.item_id) + and v.whodunnit is not null + and v.event = 'update' + order by created_at asc; + + insert into post_versions (post_id, user_id, created_at, body, edit_reason) + select + v.item_id as post_id, + v.whodunnit::bigint as user_id, + v.created_at, + v.object::json->>'body' as body, + v.object::json->>'edit_reason' as edit_reason + from versions v + where v.item_type = 'Post' + and exists(select 1 from posts p where p.id = v.item_id) + and v.whodunnit is not null + and v.event = 'update' + order by created_at asc; + """ + + # These statements should not be run by the migration in production. + # Run them manually in psql instead. + if System.get_env("MIX_ENV") != "prod" do + for stmt <- String.split(insert_statements, "\n\n") do + execute(stmt) + end + end + end + + def down do + drop table(:comment_versions) + drop table(:post_versions) + end +end diff --git a/priv/repo/structure.sql b/priv/repo/structure.sql index 49678d33..fdd13e8f 100644 --- a/priv/repo/structure.sql +++ b/priv/repo/structure.sql @@ -272,6 +272,39 @@ CREATE SEQUENCE public.channels_id_seq ALTER SEQUENCE public.channels_id_seq OWNED BY public.channels.id; +-- +-- Name: comment_versions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.comment_versions ( + id bigint NOT NULL, + comment_id bigint NOT NULL, + user_id bigint NOT NULL, + created_at timestamp(0) without time zone NOT NULL, + body character varying(255) NOT NULL, + edit_reason character varying(255) +); + + +-- +-- Name: comment_versions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.comment_versions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: comment_versions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.comment_versions_id_seq OWNED BY public.comment_versions.id; + + -- -- Name: comments; Type: TABLE; Schema: public; Owner: - -- @@ -1337,6 +1370,39 @@ CREATE SEQUENCE public.polls_id_seq ALTER SEQUENCE public.polls_id_seq OWNED BY public.polls.id; +-- +-- Name: post_versions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.post_versions ( + id bigint NOT NULL, + post_id bigint NOT NULL, + user_id bigint NOT NULL, + created_at timestamp(0) without time zone NOT NULL, + body character varying(255) NOT NULL, + edit_reason character varying(255) +); + + +-- +-- Name: post_versions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.post_versions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: post_versions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.post_versions_id_seq OWNED BY public.post_versions.id; + + -- -- Name: posts; Type: TABLE; Schema: public; Owner: - -- @@ -2267,6 +2333,13 @@ ALTER TABLE ONLY public.badges ALTER COLUMN id SET DEFAULT nextval('public.badge ALTER TABLE ONLY public.channels ALTER COLUMN id SET DEFAULT nextval('public.channels_id_seq'::regclass); +-- +-- Name: comment_versions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.comment_versions ALTER COLUMN id SET DEFAULT nextval('public.comment_versions_id_seq'::regclass); + + -- -- Name: comments id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2428,6 +2501,13 @@ ALTER TABLE ONLY public.poll_votes ALTER COLUMN id SET DEFAULT nextval('public.p ALTER TABLE ONLY public.polls ALTER COLUMN id SET DEFAULT nextval('public.polls_id_seq'::regclass); +-- +-- Name: post_versions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.post_versions ALTER COLUMN id SET DEFAULT nextval('public.post_versions_id_seq'::regclass); + + -- -- Name: posts id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2615,6 +2695,14 @@ ALTER TABLE ONLY public.channels ADD CONSTRAINT channels_pkey PRIMARY KEY (id); +-- +-- Name: comment_versions comment_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.comment_versions + ADD CONSTRAINT comment_versions_pkey PRIMARY KEY (id); + + -- -- Name: comments comments_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2799,6 +2887,14 @@ ALTER TABLE ONLY public.polls ADD CONSTRAINT polls_pkey PRIMARY KEY (id); +-- +-- Name: post_versions post_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.post_versions + ADD CONSTRAINT post_versions_pkey PRIMARY KEY (id); + + -- -- Name: posts posts_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3003,6 +3099,20 @@ CREATE INDEX channel_live_notifications_user_id_read_index ON public.channel_liv CREATE INDEX channel_live_notifications_user_id_updated_at_desc_index ON public.channel_live_notifications USING btree (user_id, updated_at DESC); +-- +-- Name: comment_versions_comment_id_created_at_desc_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX comment_versions_comment_id_created_at_desc_index ON public.comment_versions USING btree (comment_id, created_at DESC); + + +-- +-- Name: comment_versions_user_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX comment_versions_user_id_index ON public.comment_versions USING btree (user_id); + + -- -- Name: forum_post_notifications_post_id_index; Type: INDEX; Schema: public; Owner: - -- @@ -4424,6 +4534,20 @@ CREATE INDEX moderation_logs_user_id_created_at_index ON public.moderation_logs CREATE INDEX moderation_logs_user_id_index ON public.moderation_logs USING btree (user_id); +-- +-- Name: post_versions_post_id_created_at_desc_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX post_versions_post_id_created_at_desc_index ON public.post_versions USING btree (post_id, created_at DESC); + + +-- +-- Name: post_versions_user_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX post_versions_user_id_index ON public.post_versions USING btree (user_id); + + -- -- Name: reports_system_index; Type: INDEX; Schema: public; Owner: - -- @@ -4461,6 +4585,22 @@ ALTER TABLE ONLY public.channel_live_notifications ADD CONSTRAINT channel_live_notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; +-- +-- Name: comment_versions comment_versions_comment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.comment_versions + ADD CONSTRAINT comment_versions_comment_id_fkey FOREIGN KEY (comment_id) REFERENCES public.comments(id) ON DELETE CASCADE; + + +-- +-- Name: comment_versions comment_versions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.comment_versions + ADD CONSTRAINT comment_versions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + -- -- Name: channels fk_rails_021c624081; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -5389,6 +5529,22 @@ ALTER TABLE ONLY public.moderation_logs ADD CONSTRAINT moderation_logs_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; +-- +-- Name: post_versions post_versions_post_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.post_versions + ADD CONSTRAINT post_versions_post_id_fkey FOREIGN KEY (post_id) REFERENCES public.posts(id) ON DELETE CASCADE; + + +-- +-- Name: post_versions post_versions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.post_versions + ADD CONSTRAINT post_versions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + -- -- Name: source_changes source_changes_image_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -5447,3 +5603,4 @@ INSERT INTO public."schema_migrations" (version) VALUES (20211219194836); INSERT INTO public."schema_migrations" (version) VALUES (20220321173359); INSERT INTO public."schema_migrations" (version) VALUES (20240723122759); INSERT INTO public."schema_migrations" (version) VALUES (20240728191353); +INSERT INTO public."schema_migrations" (version) VALUES (20240731132924);