Add an unique index on posts URLs

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-06-22 16:50:58 +02:00
parent 8caf1e302b
commit 7bb8568504
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
4 changed files with 108 additions and 0 deletions

View file

@ -96,6 +96,7 @@ defmodule Mobilizon.Posts.Post do
|> TitleSlug.unique_constraint()
|> maybe_generate_url()
|> validate_required(@required_attrs -- [:slug, :url])
|> unique_constraint(:url)
end
defp maybe_generate_id(%Ecto.Changeset{} = changeset) do

View file

@ -0,0 +1,73 @@
defmodule Mobilizon.Storage.Repo.Migrations.CleanupPosts do
use Ecto.Migration
def up do
# Make sure we don't have any duplicate posts
rows = fetch_bad_rows()
Enum.each(rows, &process_row/1)
end
def down do
# No way down
end
defp fetch_bad_rows() do
%Postgrex.Result{rows: rows} =
Ecto.Adapters.SQL.query!(
Mobilizon.Storage.Repo,
"SELECT * FROM (
SELECT id, url,
ROW_NUMBER() OVER(PARTITION BY url ORDER BY id asc) AS Row
FROM posts
) dups
WHERE dups.Row > 1;"
)
rows
end
defp process_row([id, url, _row]) do
first_id = find_first_post_id(url)
if id != first_id do
repair_post_medias(id, first_id)
repair_post_tags(id, first_id)
delete_row(id)
end
end
defp find_first_post_id(url) do
%Postgrex.Result{rows: [[id]]} =
Ecto.Adapters.SQL.query!(
Mobilizon.Storage.Repo,
"SELECT id FROM posts WHERE url = $1 order by inserted_at asc limit 1",
[url]
)
id
end
defp repair_post_medias(id, first_id) do
Ecto.Adapters.SQL.query!(
Mobilizon.Storage.Repo,
"UPDATE post_medias SET post_id = $1 WHERE post_id = $2",
[first_id, id]
)
end
defp repair_post_tags(id, first_id) do
Ecto.Adapters.SQL.query!(
Mobilizon.Storage.Repo,
"UPDATE post_tags SET post_id = $1 WHERE post_id = $2",
[first_id, id]
)
end
defp delete_row(id) do
Ecto.Adapters.SQL.query!(
Mobilizon.Storage.Repo,
"DELETE FROM posts WHERE id = $1",
[id]
)
end
end

View file

@ -0,0 +1,11 @@
defmodule Mobilizon.Storage.Repo.Migrations.AddIndexToPosts do
use Ecto.Migration
def up do
create_if_not_exists(unique_index("posts", [:url]))
end
def down do
drop_if_exists(index("posts", [:url]))
end
end

View file

@ -42,6 +42,29 @@ defmodule Mobilizon.PostsTest do
end
end
test "create_post/1 with an already existing URL returns error changeset" do
%Actor{} = actor = insert(:actor)
%Actor{} = group = insert(:group)
post_data =
Map.merge(@valid_attrs, %{
author_id: actor.id,
attributed_to_id: group.id,
url: "https://remote.tld/p/post"
})
{:ok, %Post{} = _post} = Posts.create_post(post_data)
assert {:error,
%Ecto.Changeset{
errors: [
url:
{"has already been taken",
[constraint: :unique, constraint_name: "posts_url_index"]}
]
}} = Posts.create_post(post_data)
end
test "create_post/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Posts.create_post(@invalid_attrs)
end