From 52934d498f30b8c1be9a7a531d81780bf3ba1336 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Mon, 14 Jun 2021 18:20:35 +0800 Subject: [PATCH 1/6] refactor(cite-task): support cite artilce in side comment --- .../cms/delegates/article_curd.ex | 8 ++- .../cms/delegates/cite_tasks.ex | 56 ++++++++++++++++--- .../cms/delegates/comment_curd.ex | 5 +- .../cms/cite_contents/cite_blog_test.exs | 32 +++++++++++ .../cms/cite_contents/cite_job_test.exs | 32 +++++++++++ .../cms/cite_contents/cite_post_test.exs | 33 +++++++++++ .../cms/comments/blog_comment_test.exs | 6 +- .../cms/comments/job_comment_test.exs | 6 +- .../cms/comments/post_comment_test.exs | 6 +- .../cms/comments/repo_comment_test.exs | 6 +- 10 files changed, 163 insertions(+), 27 deletions(-) diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index 4abda48e2..4c4da0ee3 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -164,10 +164,10 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do |> Multi.run(:update_user_published_meta, fn _, _ -> Accounts.update_published_states(uid, thread) end) - |> Multi.run(:block_tasks, fn _, %{create_article: article} -> + |> Multi.run(:after_tasks, fn _, %{create_article: article} -> Later.run({CiteTasks, :handle, [article]}) + Later.run({__MODULE__, :notify_admin_new_article, [article]}) end) - # TODO: run mini tasks |> Multi.run(:mention_users, fn _, %{create_article: article} -> # article.body |> Jason.decode!() |> 各种小 task Delivery.mention_from_content(community.raw, thread, article, attrs, %User{id: uid}) @@ -223,6 +223,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do |> Multi.run(:update_edit_status, fn _, %{update_article: update_article} -> ArticleCommunity.update_edit_status(update_article) end) + |> Multi.run(:after_tasks, fn _, %{update_article: update_article} -> + Later.run({CiteTasks, :handle, [update_article]}) + end) |> Repo.transaction() |> result() end @@ -449,7 +452,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do # create done defp result({:ok, %{set_active_at_timestamp: result}}) do - Later.run({__MODULE__, :notify_admin_new_article, [result]}) {:ok, result} end diff --git a/lib/groupher_server/cms/delegates/cite_tasks.ex b/lib/groupher_server/cms/delegates/cite_tasks.ex index f816a4570..a08a30cd0 100644 --- a/lib/groupher_server/cms/delegates/cite_tasks.ex +++ b/lib/groupher_server/cms/delegates/cite_tasks.ex @@ -29,7 +29,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do import Helper.ErrorCode alias GroupherServer.{CMS, Repo} - alias CMS.Model.CitedContent + alias CMS.Model.{CitedContent, Comment} alias Helper.ORM alias Ecto.Multi @@ -38,16 +38,16 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do @article_threads get_config(:article, :threads) @valid_article_prefix Enum.map(@article_threads, &"#{@site_host}/#{&1}/") - def handle(%{body: body} = article) do + def handle(%{body: body} = content) do with {:ok, %{"blocks" => blocks}} <- Jason.decode(body), - article <- Repo.preload(article, author: :user) do + content <- preload_content_author(content) do Multi.new() |> Multi.run(:delete_all_cited_contents, fn _, _ -> - delete_all_cited_contents(article) + delete_all_cited_contents(content) end) |> Multi.run(:update_cited_info, fn _, _ -> blocks - |> Enum.reduce([], &(&2 ++ parse_cited_info_per_block(article, &1))) + |> Enum.reduce([], &(&2 ++ parse_cited_info_per_block(content, &1))) |> merge_same_cited_article_block |> update_cited_info end) @@ -56,9 +56,17 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do end end + def preload_content_author(%Comment{} = comment), do: comment + def preload_content_author(article), do: Repo.preload(article, author: :user) + # delete all records before insert_all, this will dynamiclly update # those cited info when update article # 插入引用记录之前先全部清除,这样可以在更新文章的时候自动计算引用信息 + defp delete_all_cited_contents(%Comment{} = comment) do + query = from(c in CitedContent, where: c.comment_id == ^comment.id) + ORM.delete_all(query, :if_exist) + end + defp delete_all_cited_contents(article) do with {:ok, thread} <- thread_of_article(article), {:ok, info} <- match(thread) do @@ -73,6 +81,8 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do defp update_cited_info(cited_contents) do clean_cited_contents = Enum.map(cited_contents, &Map.delete(&1, :cited_article)) # IO.inspect(clean_cited_contents, label: "clean_cited_contents") + # IO.inspect(cited_contents, label: "cited_contents") + with true <- {0, nil} !== Repo.insert_all(CitedContent, clean_cited_contents) do update_citing_count(cited_contents) else @@ -148,9 +158,27 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do ] """ defp parse_cited_info_per_block(article, %{"id" => block_id, "data" => %{"text" => text}}) do - links_in_block = Floki.find(text, "a[href]") + links = Floki.find(text, "a[href]") + + do_parse_cited_info(article, block_id, links) + end + + defp do_parse_cited_info(%Comment{} = comment, block_id, links) do + Enum.reduce(links, [], fn link, acc -> + with {:ok, cited_article} <- parse_cited_article(link) do + List.insert_at(acc, 0, shape_cited_content(comment, cited_article, block_id)) + else + _ -> acc + end + end) + |> Enum.uniq() + end - Enum.reduce(links_in_block, [], fn link, acc -> + # links Floki parsed fmt + # e.g: + # [{"a", [{"href", "https://coderplanets.com/post/195675"}], []},] + defp do_parse_cited_info(article, block_id, links) do + Enum.reduce(links, [], fn link, acc -> with {:ok, cited_article} <- parse_cited_article(link), # do not cite artilce itself true <- article.id !== cited_article.id do @@ -162,6 +190,20 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do |> Enum.uniq() end + # 在评论中引用文章的情况 + defp shape_cited_content(%Comment{} = comment, cited_article, block_id) do + %{ + cited_by_id: cited_article.id, + cited_by_type: cited_article.meta.thread, + # used for updating citing_count, avoid load again + cited_article: cited_article, + comment_id: comment.id, + block_linker: [block_id], + user_id: comment.author_id + } + end + + # 文章之间相互引用的情况 defp shape_cited_content(article, cited_article, block_id) do {:ok, thread} = thread_of_article(article) {:ok, info} = match(thread) diff --git a/lib/groupher_server/cms/delegates/comment_curd.ex b/lib/groupher_server/cms/delegates/comment_curd.ex index ce05f3c9e..80b0fb18e 100644 --- a/lib/groupher_server/cms/delegates/comment_curd.ex +++ b/lib/groupher_server/cms/delegates/comment_curd.ex @@ -11,7 +11,7 @@ defmodule GroupherServer.CMS.Delegate.CommentCurd do import ShortMaps alias Helper.Types, as: T - alias Helper.{ORM, QueryBuilder, Converter} + alias Helper.{Later, ORM, QueryBuilder, Converter} alias GroupherServer.{Accounts, CMS, Repo} alias CMS.Model.Post @@ -113,6 +113,9 @@ defmodule GroupherServer.CMS.Delegate.CommentCurd do false -> CMS.update_active_timestamp(thread, article) end end) + |> Multi.run(:block_tasks, fn _, %{create_comment: create_comment} -> + Later.run({CiteTasks, :handle, [create_comment]}) + end) |> Repo.transaction() |> result() else diff --git a/test/groupher_server/cms/cite_contents/cite_blog_test.exs b/test/groupher_server/cms/cite_contents/cite_blog_test.exs index b4188fcff..1c6972852 100644 --- a/test/groupher_server/cms/cite_contents/cite_blog_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_blog_test.exs @@ -73,5 +73,37 @@ defmodule GroupherServer.Test.CMS.CiteContent.Blog do {:ok, blog} = ORM.find(Blog, blog.id) assert blog.meta.citing_count == 0 end + + @tag :wip + test "can cited blog inside a comment", ~m(user blog blog2 blog3 blog4 blog5)a do + comment_body = + mock_rich_text( + ~s(the and same la is awesome, the is awesome too.), + # second paragraph + ~s(the paragraph 2 again, the paragraph 2 again, the paragraph 2 again) + ) + + {:ok, comment} = CMS.create_comment(:blog, blog.id, comment_body, user) + CiteTasks.handle(comment) + + comment_body = mock_rich_text(~s(the )) + {:ok, comment} = CMS.create_comment(:blog, blog.id, comment_body, user) + + CiteTasks.handle(comment) + + {:ok, blog2} = ORM.find(Blog, blog2.id) + {:ok, blog3} = ORM.find(Blog, blog3.id) + {:ok, blog4} = ORM.find(Blog, blog4.id) + {:ok, blog5} = ORM.find(Blog, blog5.id) + + assert blog2.meta.citing_count == 1 + assert blog3.meta.citing_count == 2 + assert blog4.meta.citing_count == 1 + assert blog5.meta.citing_count == 1 + end end end diff --git a/test/groupher_server/cms/cite_contents/cite_job_test.exs b/test/groupher_server/cms/cite_contents/cite_job_test.exs index 3bd197b4f..d3043d8a5 100644 --- a/test/groupher_server/cms/cite_contents/cite_job_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_job_test.exs @@ -73,5 +73,37 @@ defmodule GroupherServer.Test.CMS.CiteContent.Job do {:ok, job} = ORM.find(Job, job.id) assert job.meta.citing_count == 0 end + + @tag :wip + test "can cited job inside a comment", ~m(user job job2 job3 job4 job5)a do + comment_body = + mock_rich_text( + ~s(the and same la is awesome, the is awesome too.), + # second paragraph + ~s(the paragraph 2 again, the paragraph 2 again, the paragraph 2 again) + ) + + {:ok, comment} = CMS.create_comment(:job, job.id, comment_body, user) + CiteTasks.handle(comment) + + comment_body = mock_rich_text(~s(the )) + {:ok, comment} = CMS.create_comment(:job, job.id, comment_body, user) + + CiteTasks.handle(comment) + + {:ok, job2} = ORM.find(Job, job2.id) + {:ok, job3} = ORM.find(Job, job3.id) + {:ok, job4} = ORM.find(Job, job4.id) + {:ok, job5} = ORM.find(Job, job5.id) + + assert job2.meta.citing_count == 1 + assert job3.meta.citing_count == 2 + assert job4.meta.citing_count == 1 + assert job5.meta.citing_count == 1 + end end end diff --git a/test/groupher_server/cms/cite_contents/cite_post_test.exs b/test/groupher_server/cms/cite_contents/cite_post_test.exs index 2db3c4a81..97bf84642 100644 --- a/test/groupher_server/cms/cite_contents/cite_post_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_post_test.exs @@ -29,6 +29,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Post do end describe "[cite basic]" do + # @tag :wip test "cited multi post should work", ~m(user community post2 post3 post4 post5 post_attrs)a do body = mock_rich_text( @@ -73,5 +74,37 @@ defmodule GroupherServer.Test.CMS.CiteContent.Post do {:ok, post} = ORM.find(Post, post.id) assert post.meta.citing_count == 0 end + + @tag :wip + test "can cited post inside a comment", ~m(user post post2 post3 post4 post5)a do + comment_body = + mock_rich_text( + ~s(the and same la is awesome, the is awesome too.), + # second paragraph + ~s(the paragraph 2 again, the paragraph 2 again, the paragraph 2 again) + ) + + {:ok, comment} = CMS.create_comment(:post, post.id, comment_body, user) + CiteTasks.handle(comment) + + comment_body = mock_rich_text(~s(the )) + {:ok, comment} = CMS.create_comment(:post, post.id, comment_body, user) + + CiteTasks.handle(comment) + + {:ok, post2} = ORM.find(Post, post2.id) + {:ok, post3} = ORM.find(Post, post3.id) + {:ok, post4} = ORM.find(Post, post4.id) + {:ok, post5} = ORM.find(Post, post5.id) + + assert post2.meta.citing_count == 1 + assert post3.meta.citing_count == 2 + assert post4.meta.citing_count == 1 + assert post5.meta.citing_count == 1 + end end end diff --git a/test/groupher_server_web/mutation/cms/comments/blog_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/blog_comment_test.exs index 088c18ec5..a9c663fb4 100644 --- a/test/groupher_server_web/mutation/cms/comments/blog_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/blog_comment_test.exs @@ -279,7 +279,7 @@ defmodule GroupherServer.Test.Mutation.Comments.BlogComment do } } """ - @tag :wip + test "can pin a blog's comment", ~m(owner_conn blog user)a do {:ok, comment} = CMS.create_comment(:blog, blog.id, mock_comment(), user) @@ -290,7 +290,6 @@ defmodule GroupherServer.Test.Mutation.Comments.BlogComment do assert result["isPinned"] end - @tag :wip test "unauth user fails.", ~m(guest_conn blog user)a do {:ok, comment} = CMS.create_comment(:blog, blog.id, mock_comment(), user) variables = %{id: comment.id} @@ -306,7 +305,7 @@ defmodule GroupherServer.Test.Mutation.Comments.BlogComment do } } """ - @tag :wip + test "can undo pin a blog's comment", ~m(owner_conn blog user)a do {:ok, comment} = CMS.create_comment(:blog, blog.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) @@ -318,7 +317,6 @@ defmodule GroupherServer.Test.Mutation.Comments.BlogComment do assert not result["isPinned"] end - @tag :wip test "unauth user undo fails.", ~m(guest_conn blog user)a do {:ok, comment} = CMS.create_comment(:blog, blog.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) diff --git a/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs index e9dee6182..4076c3930 100644 --- a/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs @@ -279,7 +279,7 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do } } """ - @tag :wip + test "can pin a job's comment", ~m(owner_conn job user)a do {:ok, comment} = CMS.create_comment(:job, job.id, mock_comment(), user) @@ -290,7 +290,6 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do assert result["isPinned"] end - @tag :wip test "unauth user fails.", ~m(guest_conn job user)a do {:ok, comment} = CMS.create_comment(:job, job.id, mock_comment(), user) variables = %{id: comment.id} @@ -306,7 +305,7 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do } } """ - @tag :wip + test "can undo pin a job's comment", ~m(owner_conn job user)a do {:ok, comment} = CMS.create_comment(:job, job.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) @@ -318,7 +317,6 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do assert not result["isPinned"] end - @tag :wip test "unauth user undo fails.", ~m(guest_conn job user)a do {:ok, comment} = CMS.create_comment(:job, job.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) diff --git a/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs index 644efe330..43f46e4dd 100644 --- a/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs @@ -280,7 +280,7 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do } } """ - @tag :wip + test "can pin a post's comment", ~m(owner_conn post user)a do {:ok, comment} = CMS.create_comment(:post, post.id, mock_comment(), user) @@ -291,7 +291,6 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do assert result["isPinned"] end - @tag :wip test "unauth user fails.", ~m(guest_conn post user)a do {:ok, comment} = CMS.create_comment(:post, post.id, mock_comment(), user) variables = %{id: comment.id} @@ -307,7 +306,7 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do } } """ - @tag :wip + test "can undo pin a post's comment", ~m(owner_conn post user)a do {:ok, comment} = CMS.create_comment(:post, post.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) @@ -319,7 +318,6 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do assert not result["isPinned"] end - @tag :wip test "unauth user undo fails.", ~m(guest_conn post user)a do {:ok, comment} = CMS.create_comment(:post, post.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) diff --git a/test/groupher_server_web/mutation/cms/comments/repo_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/repo_comment_test.exs index f7b0ab710..e5304b1a6 100644 --- a/test/groupher_server_web/mutation/cms/comments/repo_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/repo_comment_test.exs @@ -279,7 +279,7 @@ defmodule GroupherServer.Test.Mutation.Comments.RepoComment do } } """ - @tag :wip + test "can pin a repo's comment", ~m(owner_conn repo user)a do {:ok, comment} = CMS.create_comment(:repo, repo.id, mock_comment(), user) @@ -290,7 +290,6 @@ defmodule GroupherServer.Test.Mutation.Comments.RepoComment do assert result["isPinned"] end - @tag :wip test "unauth user fails.", ~m(guest_conn repo user)a do {:ok, comment} = CMS.create_comment(:repo, repo.id, mock_comment(), user) variables = %{id: comment.id} @@ -306,7 +305,7 @@ defmodule GroupherServer.Test.Mutation.Comments.RepoComment do } } """ - @tag :wip + test "can undo pin a repo's comment", ~m(owner_conn repo user)a do {:ok, comment} = CMS.create_comment(:repo, repo.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) @@ -318,7 +317,6 @@ defmodule GroupherServer.Test.Mutation.Comments.RepoComment do assert not result["isPinned"] end - @tag :wip test "unauth user undo fails.", ~m(guest_conn repo user)a do {:ok, comment} = CMS.create_comment(:repo, repo.id, mock_comment(), user) {:ok, _} = CMS.pin_comment(comment.id) From e97cce1edbfb799ac50d2db5d243600407df640a Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 15 Jun 2021 00:02:38 +0800 Subject: [PATCH 2/6] refactor(cite-task): support cross comment citing --- .../cms/delegates/cite_tasks.ex | 139 ++++++++++++++---- .../cms/models/cited_content.ex | 2 +- .../cms/models/embeds/comment_meta.ex | 6 +- ...39_missing_timestamp_for_cite_contents.exs | 9 ++ .../cms/cite_contents/cite_blog_test.exs | 44 +++++- .../cms/cite_contents/cite_job_test.exs | 44 +++++- .../cms/cite_contents/cite_post_test.exs | 46 +++++- 7 files changed, 253 insertions(+), 37 deletions(-) create mode 100644 priv/repo/migrations/20210614145939_missing_timestamp_for_cite_contents.exs diff --git a/lib/groupher_server/cms/delegates/cite_tasks.ex b/lib/groupher_server/cms/delegates/cite_tasks.ex index a08a30cd0..fd2de60aa 100644 --- a/lib/groupher_server/cms/delegates/cite_tasks.ex +++ b/lib/groupher_server/cms/delegates/cite_tasks.ex @@ -79,9 +79,12 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # defp batch_done defp update_cited_info(cited_contents) do - clean_cited_contents = Enum.map(cited_contents, &Map.delete(&1, :cited_article)) - # IO.inspect(clean_cited_contents, label: "clean_cited_contents") - # IO.inspect(cited_contents, label: "cited_contents") + # see: https://github.com/elixir-ecto/ecto/issues/1932#issuecomment-314083252 + clean_cited_contents = + cited_contents + |> Enum.map(&(&1 |> Map.merge(%{inserted_at: &1.citing_time, updated_at: &1.citing_time}))) + |> Enum.map(&Map.delete(&1, :cited_content)) + |> Enum.map(&Map.delete(&1, :citing_time)) with true <- {0, nil} !== Repo.insert_all(CitedContent, clean_cited_contents) do update_citing_count(cited_contents) @@ -95,10 +98,10 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do count_query = from(c in CitedContent, where: c.cited_by_id == ^content.cited_by_id) count = Repo.aggregate(count_query, :count) - cited_article = content.cited_article - meta = Map.merge(cited_article.meta, %{citing_count: count}) + cited_content = content.cited_content + meta = Map.merge(cited_content.meta, %{citing_count: count}) - case cited_article |> ORM.update_meta(meta) do + case cited_content |> ORM.update_meta(meta) do {:ok, _} -> true {:error, _} -> false end @@ -150,23 +153,26 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do block_linker: ["block-ZgKJs"], cited_by_id: 190057, cited_by_type: "POST", - cited_article: #loaded, + cited_content: #loaded, post_id: 190059, user_id: 1413053 } ... ] """ - defp parse_cited_info_per_block(article, %{"id" => block_id, "data" => %{"text" => text}}) do + defp parse_cited_info_per_block(content, %{"id" => block_id, "data" => %{"text" => text}}) do links = Floki.find(text, "a[href]") - do_parse_cited_info(article, block_id, links) + do_parse_cited_info(content, block_id, links) end defp do_parse_cited_info(%Comment{} = comment, block_id, links) do + # IO.inspect(links, label: "links -> ") + Enum.reduce(links, [], fn link, acc -> - with {:ok, cited_article} <- parse_cited_article(link) do - List.insert_at(acc, 0, shape_cited_content(comment, cited_article, block_id)) + with {:ok, cited_content} <- parse_cited_content(link) do + # IO.inspect(cited_content, label: "before shape cited_content") + List.insert_at(acc, 0, shape_cited_content(comment, cited_content, block_id)) else _ -> acc end @@ -179,10 +185,10 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # [{"a", [{"href", "https://coderplanets.com/post/195675"}], []},] defp do_parse_cited_info(article, block_id, links) do Enum.reduce(links, [], fn link, acc -> - with {:ok, cited_article} <- parse_cited_article(link), - # do not cite artilce itself - true <- article.id !== cited_article.id do - List.insert_at(acc, 0, shape_cited_content(article, cited_article, block_id)) + with {:ok, cited_content} <- parse_cited_content(link) do + # do not cite artilce itself + # true <- article.id !== cited_content.id do + List.insert_at(acc, 0, shape_cited_content(article, cited_content, block_id)) else _ -> acc end @@ -190,39 +196,91 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do |> Enum.uniq() end - # 在评论中引用文章的情况 - defp shape_cited_content(%Comment{} = comment, cited_article, block_id) do + # 在评论中引用文章 + defp shape_cited_content( + %Comment{} = comment, + %{type: :article, content: cited_article}, + block_id + ) do %{ cited_by_id: cited_article.id, cited_by_type: cited_article.meta.thread, + comment_id: comment.id, + block_linker: [block_id], + user_id: comment.author_id, # used for updating citing_count, avoid load again - cited_article: cited_article, + cited_content: cited_article, + # for later insert all + citing_time: comment.updated_at |> DateTime.truncate(:second) + } + end + + # 评论中引用评论 + defp shape_cited_content( + %Comment{} = comment, + %{type: :comment, content: cited_comment}, + block_id + ) do + %{ + cited_by_id: cited_comment.id, + cited_by_type: "COMMENT", comment_id: comment.id, block_linker: [block_id], - user_id: comment.author_id + user_id: comment.author_id, + # used for updating citing_count, avoid load again + cited_content: cited_comment, + # for later insert all + citing_time: comment.updated_at |> DateTime.truncate(:second) } end - # 文章之间相互引用的情况 - defp shape_cited_content(article, cited_article, block_id) do + # 文章之间相互引用 + defp shape_cited_content(article, %{type: :article, content: cited_article}, block_id) do {:ok, thread} = thread_of_article(article) {:ok, info} = match(thread) %{ cited_by_id: cited_article.id, cited_by_type: cited_article.meta.thread, + block_linker: [block_id], + user_id: article.author.user.id, # used for updating citing_count, avoid load again - cited_article: cited_article, + cited_content: cited_article, + # for later insert all + citing_time: article.updated_at |> DateTime.truncate(:second) + } + |> Map.put(info.foreign_key, article.id) + end + + # 文章里引用评论 + defp shape_cited_content(article, %{type: :comment, content: cited_comment}, block_id) do + {:ok, thread} = thread_of_article(article) + {:ok, info} = match(thread) + + %{ + cited_by_id: cited_comment.id, + cited_by_type: "COMMENT", block_linker: [block_id], - user_id: article.author.user.id + user_id: article.author.user.id, + # used for updating citing_count, avoid load again + cited_content: cited_comment, + # for later insert all + citing_time: article.updated_at |> DateTime.truncate(:second) } |> Map.put(info.foreign_key, article.id) end - defp parse_cited_article({"a", attrs, _}) do + # 要考虑是否有 comment_id 的情况,如果有,那么 就应该 load comment 而不是 article + defp parse_cited_content({"a", attrs, _}) do with {:ok, link} <- parse_link(attrs), true <- is_site_article_link?(link) do - load_cited_article_from_url(link) + # IO.inspect(link, label: "parse link") + # IO.inspect(is_comment_link?(link), label: "is_comment_link") + + case is_comment_link?(link) do + true -> load_cited_comment_from_url(link) + false -> load_cited_article_from_url(link) + end end end @@ -246,6 +304,32 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do Enum.any?(@valid_article_prefix, &String.starts_with?(url, &1)) end + defp is_comment_link?(url) do + with %{query: query} <- URI.parse(url) do + not is_nil(query) and String.starts_with?(query, "comment_id=") + # is_valid_query = not is_nil(query) and String.starts_with?(query, "comment_id=") + # comment_id = URI.decode_query(query) |> Map.get("comment_id") + # query_not_empty = comment_id + + # URI.decode_query(query) |> String.starts_with?("comment_id=") + # IO.inspect(query, label: "the query") + end + end + + defp load_cited_comment_from_url(url) do + %{query: query} = URI.parse(url) + + try do + comment_id = URI.decode_query(query) |> Map.get("comment_id") + + with {:ok, comment} <- ORM.find(Comment, comment_id) do + {:ok, %{type: :comment, content: comment}} + end + rescue + _ -> {:error, "load comment error"} + end + end + # get cited article from url # e.g: https://coderplanets.com/post/189993 -> ORM.find(Post, 189993) defp load_cited_article_from_url(url) do @@ -254,8 +338,9 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do thread = path_list |> Enum.at(1) |> String.downcase() |> String.to_atom() article_id = path_list |> Enum.at(2) - with {:ok, info} <- match(thread) do - ORM.find(info.model, article_id) + with {:ok, info} <- match(thread), + {:ok, article} <- ORM.find(info.model, article_id) do + {:ok, %{type: :article, content: article}} end end diff --git a/lib/groupher_server/cms/models/cited_content.ex b/lib/groupher_server/cms/models/cited_content.ex index 452dde2ee..545a8e58c 100644 --- a/lib/groupher_server/cms/models/cited_content.ex +++ b/lib/groupher_server/cms/models/cited_content.ex @@ -13,7 +13,7 @@ defmodule GroupherServer.CMS.Model.CitedContent do alias CMS.Model.Comment - @timestamps_opts [type: :utc_datetime_usec] + @timestamps_opts [type: :utc_datetime] @required_fields ~w(cited_by_type cited_by_id user_id)a @article_cast_fields general_article_fields(:cast) diff --git a/lib/groupher_server/cms/models/embeds/comment_meta.ex b/lib/groupher_server/cms/models/embeds/comment_meta.ex index bb5a830d1..6ff4be0f0 100644 --- a/lib/groupher_server/cms/models/embeds/comment_meta.ex +++ b/lib/groupher_server/cms/models/embeds/comment_meta.ex @@ -7,7 +7,7 @@ defmodule GroupherServer.CMS.Model.Embeds.CommentMeta do import Ecto.Changeset - @optional_fields ~w(is_article_author_upvoted report_count is_reply_to_others reported_count reported_user_ids)a + @optional_fields ~w(is_article_author_upvoted report_count is_reply_to_others reported_count reported_user_ids citing_count)a @doc "for test usage" def default_meta() do @@ -17,7 +17,8 @@ defmodule GroupherServer.CMS.Model.Embeds.CommentMeta do report_count: 0, upvoted_user_ids: [], reported_user_ids: [], - reported_count: 0 + reported_count: 0, + citing_count: 0 } end @@ -31,6 +32,7 @@ defmodule GroupherServer.CMS.Model.Embeds.CommentMeta do field(:upvoted_user_ids, {:array, :integer}, default: []) field(:reported_user_ids, {:array, :integer}, default: []) field(:reported_count, :integer, default: 0) + field(:citing_count, :integer, default: 0) end def changeset(struct, params) do diff --git a/priv/repo/migrations/20210614145939_missing_timestamp_for_cite_contents.exs b/priv/repo/migrations/20210614145939_missing_timestamp_for_cite_contents.exs new file mode 100644 index 000000000..843bd53b6 --- /dev/null +++ b/priv/repo/migrations/20210614145939_missing_timestamp_for_cite_contents.exs @@ -0,0 +1,9 @@ +defmodule GroupherServer.Repo.Migrations.MissingTimestampForCiteContents do + use Ecto.Migration + + def change do + alter table(:cited_contents) do + timestamps() + end + end +end diff --git a/test/groupher_server/cms/cite_contents/cite_blog_test.exs b/test/groupher_server/cms/cite_contents/cite_blog_test.exs index 1c6972852..c8dffaffa 100644 --- a/test/groupher_server/cms/cite_contents/cite_blog_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_blog_test.exs @@ -6,8 +6,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Blog do alias Helper.ORM alias GroupherServer.CMS - alias CMS.Model.Blog - + alias CMS.Model.{Blog, Comment, CitedContent} alias CMS.Delegate.CiteTasks @site_host get_config(:general, :site_host) @@ -75,6 +74,47 @@ defmodule GroupherServer.Test.CMS.CiteContent.Blog do end @tag :wip + test "can cite blog's comment in blog", ~m(community user blog blog2 blog_attrs)a do + {:ok, comment} = CMS.create_comment(:blog, blog.id, mock_rich_text("hello"), user) + + body = + mock_rich_text(~s(the )) + + blog_attrs = blog_attrs |> Map.merge(%{body: body}) + + {:ok, blog} = CMS.create_article(community, :blog, blog_attrs, user) + CiteTasks.handle(blog) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.meta.citing_count == 1 + + {:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: comment.id}) + assert blog.id == cite_content.blog_id + assert cite_content.cited_by_type == "COMMENT" + end + + @tag :wip + test "can cite a comment in a comment", ~m(user blog)a do + {:ok, cited_comment} = CMS.create_comment(:blog, blog.id, mock_rich_text("hello"), user) + + comment_body = + mock_rich_text( + ~s(the ) + ) + + {:ok, comment} = CMS.create_comment(:blog, blog.id, comment_body, user) + + CiteTasks.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 1 + + {:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: cited_comment.id}) + assert comment.id == cite_content.comment_id + assert cited_comment.id == cite_content.cited_by_id + assert cite_content.cited_by_type == "COMMENT" + end + test "can cited blog inside a comment", ~m(user blog blog2 blog3 blog4 blog5)a do comment_body = mock_rich_text( diff --git a/test/groupher_server/cms/cite_contents/cite_job_test.exs b/test/groupher_server/cms/cite_contents/cite_job_test.exs index d3043d8a5..9bdeb8ffc 100644 --- a/test/groupher_server/cms/cite_contents/cite_job_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_job_test.exs @@ -6,8 +6,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Job do alias Helper.ORM alias GroupherServer.CMS - alias CMS.Model.Job - + alias CMS.Model.{Job, Comment, CitedContent} alias CMS.Delegate.CiteTasks @site_host get_config(:general, :site_host) @@ -75,6 +74,47 @@ defmodule GroupherServer.Test.CMS.CiteContent.Job do end @tag :wip + test "can cite job's comment in job", ~m(community user job job2 job_attrs)a do + {:ok, comment} = CMS.create_comment(:job, job.id, mock_rich_text("hello"), user) + + body = + mock_rich_text(~s(the )) + + job_attrs = job_attrs |> Map.merge(%{body: body}) + + {:ok, job} = CMS.create_article(community, :job, job_attrs, user) + CiteTasks.handle(job) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.meta.citing_count == 1 + + {:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: comment.id}) + assert job.id == cite_content.job_id + assert cite_content.cited_by_type == "COMMENT" + end + + @tag :wip + test "can cite a comment in a comment", ~m(user job)a do + {:ok, cited_comment} = CMS.create_comment(:job, job.id, mock_rich_text("hello"), user) + + comment_body = + mock_rich_text( + ~s(the ) + ) + + {:ok, comment} = CMS.create_comment(:job, job.id, comment_body, user) + + CiteTasks.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 1 + + {:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: cited_comment.id}) + assert comment.id == cite_content.comment_id + assert cited_comment.id == cite_content.cited_by_id + assert cite_content.cited_by_type == "COMMENT" + end + test "can cited job inside a comment", ~m(user job job2 job3 job4 job5)a do comment_body = mock_rich_text( diff --git a/test/groupher_server/cms/cite_contents/cite_post_test.exs b/test/groupher_server/cms/cite_contents/cite_post_test.exs index 97bf84642..0f1d6674b 100644 --- a/test/groupher_server/cms/cite_contents/cite_post_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_post_test.exs @@ -6,8 +6,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Post do alias Helper.ORM alias GroupherServer.CMS - alias CMS.Model.Post - + alias CMS.Model.{Post, Comment, CitedContent} alias CMS.Delegate.CiteTasks @site_host get_config(:general, :site_host) @@ -29,7 +28,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Post do end describe "[cite basic]" do - # @tag :wip + # test "cited multi post should work", ~m(user community post2 post3 post4 post5 post_attrs)a do body = mock_rich_text( @@ -76,6 +75,47 @@ defmodule GroupherServer.Test.CMS.CiteContent.Post do end @tag :wip + test "can cite post's comment in post", ~m(community user post post2 post_attrs)a do + {:ok, comment} = CMS.create_comment(:post, post.id, mock_rich_text("hello"), user) + + body = + mock_rich_text(~s(the )) + + post_attrs = post_attrs |> Map.merge(%{body: body}) + + {:ok, post} = CMS.create_article(community, :post, post_attrs, user) + CiteTasks.handle(post) + + {:ok, comment} = ORM.find(Comment, comment.id) + assert comment.meta.citing_count == 1 + + {:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: comment.id}) + assert post.id == cite_content.post_id + assert cite_content.cited_by_type == "COMMENT" + end + + @tag :wip + test "can cite a comment in a comment", ~m(user post)a do + {:ok, cited_comment} = CMS.create_comment(:post, post.id, mock_rich_text("hello"), user) + + comment_body = + mock_rich_text( + ~s(the ) + ) + + {:ok, comment} = CMS.create_comment(:post, post.id, comment_body, user) + + CiteTasks.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 1 + + {:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: cited_comment.id}) + assert comment.id == cite_content.comment_id + assert cited_comment.id == cite_content.cited_by_id + assert cite_content.cited_by_type == "COMMENT" + end + test "can cited post inside a comment", ~m(user post post2 post3 post4 post5)a do comment_body = mock_rich_text( From 485335e47ecb9c6e7ecc5ede97cb29ec27116c32 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 15 Jun 2021 00:10:14 +0800 Subject: [PATCH 3/6] refactor(cite-task): add some inline doc --- lib/groupher_server/cms/delegates/cite_tasks.ex | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/groupher_server/cms/delegates/cite_tasks.ex b/lib/groupher_server/cms/delegates/cite_tasks.ex index fd2de60aa..01838f8b1 100644 --- a/lib/groupher_server/cms/delegates/cite_tasks.ex +++ b/lib/groupher_server/cms/delegates/cite_tasks.ex @@ -196,6 +196,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do |> Enum.uniq() end + # cite article in comment # 在评论中引用文章 defp shape_cited_content( %Comment{} = comment, @@ -208,6 +209,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do comment_id: comment.id, block_linker: [block_id], user_id: comment.author_id, + # extra fields for next-step usage # used for updating citing_count, avoid load again cited_content: cited_article, # for later insert all @@ -215,6 +217,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do } end + # cite comment in comment # 评论中引用评论 defp shape_cited_content( %Comment{} = comment, @@ -227,6 +230,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do comment_id: comment.id, block_linker: [block_id], user_id: comment.author_id, + # extra fields for next-step usage # used for updating citing_count, avoid load again cited_content: cited_comment, # for later insert all @@ -234,6 +238,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do } end + # cite article in article # 文章之间相互引用 defp shape_cited_content(article, %{type: :article, content: cited_article}, block_id) do {:ok, thread} = thread_of_article(article) @@ -244,6 +249,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do cited_by_type: cited_article.meta.thread, block_linker: [block_id], user_id: article.author.user.id, + # extra fields for next-step usage # used for updating citing_count, avoid load again cited_content: cited_article, # for later insert all @@ -252,7 +258,8 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do |> Map.put(info.foreign_key, article.id) end - # 文章里引用评论 + # cite comment in article + # 文章中引用评论 defp shape_cited_content(article, %{type: :comment, content: cited_comment}, block_id) do {:ok, thread} = thread_of_article(article) {:ok, info} = match(thread) @@ -262,6 +269,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do cited_by_type: "COMMENT", block_linker: [block_id], user_id: article.author.user.id, + # extra fields for next-step usage # used for updating citing_count, avoid load again cited_content: cited_comment, # for later insert all @@ -307,12 +315,6 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do defp is_comment_link?(url) do with %{query: query} <- URI.parse(url) do not is_nil(query) and String.starts_with?(query, "comment_id=") - # is_valid_query = not is_nil(query) and String.starts_with?(query, "comment_id=") - # comment_id = URI.decode_query(query) |> Map.get("comment_id") - # query_not_empty = comment_id - - # URI.decode_query(query) |> String.starts_with?("comment_id=") - # IO.inspect(query, label: "the query") end end From fa6efa1d1880aee49cbc7f702b4e5660a61c3e10 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 15 Jun 2021 00:19:39 +0800 Subject: [PATCH 4/6] refactor(cite-task): fmt naming --- .../cms/delegates/cite_tasks.ex | 51 +++++++------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/lib/groupher_server/cms/delegates/cite_tasks.ex b/lib/groupher_server/cms/delegates/cite_tasks.ex index 01838f8b1..a7c5002c1 100644 --- a/lib/groupher_server/cms/delegates/cite_tasks.ex +++ b/lib/groupher_server/cms/delegates/cite_tasks.ex @@ -170,10 +170,8 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # IO.inspect(links, label: "links -> ") Enum.reduce(links, [], fn link, acc -> - with {:ok, cited_content} <- parse_cited_content(link) do - # IO.inspect(cited_content, label: "before shape cited_content") - List.insert_at(acc, 0, shape_cited_content(comment, cited_content, block_id)) - else + case parse_cited(link) do + {:ok, cited} -> List.insert_at(acc, 0, shape_cited(comment, cited, block_id)) _ -> acc end end) @@ -185,11 +183,8 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # [{"a", [{"href", "https://coderplanets.com/post/195675"}], []},] defp do_parse_cited_info(article, block_id, links) do Enum.reduce(links, [], fn link, acc -> - with {:ok, cited_content} <- parse_cited_content(link) do - # do not cite artilce itself - # true <- article.id !== cited_content.id do - List.insert_at(acc, 0, shape_cited_content(article, cited_content, block_id)) - else + case parse_cited(link) do + {:ok, cited} -> List.insert_at(acc, 0, shape_cited(article, cited, block_id)) _ -> acc end end) @@ -198,20 +193,16 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # cite article in comment # 在评论中引用文章 - defp shape_cited_content( - %Comment{} = comment, - %{type: :article, content: cited_article}, - block_id - ) do + defp shape_cited(%Comment{} = comment, %{type: :article, content: cited}, block_id) do %{ - cited_by_id: cited_article.id, - cited_by_type: cited_article.meta.thread, + cited_by_id: cited.id, + cited_by_type: cited.meta.thread, comment_id: comment.id, block_linker: [block_id], user_id: comment.author_id, # extra fields for next-step usage # used for updating citing_count, avoid load again - cited_content: cited_article, + cited_content: cited, # for later insert all citing_time: comment.updated_at |> DateTime.truncate(:second) } @@ -219,20 +210,16 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # cite comment in comment # 评论中引用评论 - defp shape_cited_content( - %Comment{} = comment, - %{type: :comment, content: cited_comment}, - block_id - ) do + defp shape_cited(%Comment{} = comment, %{type: :comment, content: cited}, block_id) do %{ - cited_by_id: cited_comment.id, + cited_by_id: cited.id, cited_by_type: "COMMENT", comment_id: comment.id, block_linker: [block_id], user_id: comment.author_id, # extra fields for next-step usage # used for updating citing_count, avoid load again - cited_content: cited_comment, + cited_content: cited, # for later insert all citing_time: comment.updated_at |> DateTime.truncate(:second) } @@ -240,18 +227,18 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # cite article in article # 文章之间相互引用 - defp shape_cited_content(article, %{type: :article, content: cited_article}, block_id) do + defp shape_cited(article, %{type: :article, content: cited}, block_id) do {:ok, thread} = thread_of_article(article) {:ok, info} = match(thread) %{ - cited_by_id: cited_article.id, - cited_by_type: cited_article.meta.thread, + cited_by_id: cited.id, + cited_by_type: cited.meta.thread, block_linker: [block_id], user_id: article.author.user.id, # extra fields for next-step usage # used for updating citing_count, avoid load again - cited_content: cited_article, + cited_content: cited, # for later insert all citing_time: article.updated_at |> DateTime.truncate(:second) } @@ -260,18 +247,18 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do # cite comment in article # 文章中引用评论 - defp shape_cited_content(article, %{type: :comment, content: cited_comment}, block_id) do + defp shape_cited(article, %{type: :comment, content: cited}, block_id) do {:ok, thread} = thread_of_article(article) {:ok, info} = match(thread) %{ - cited_by_id: cited_comment.id, + cited_by_id: cited.id, cited_by_type: "COMMENT", block_linker: [block_id], user_id: article.author.user.id, # extra fields for next-step usage # used for updating citing_count, avoid load again - cited_content: cited_comment, + cited_content: cited, # for later insert all citing_time: article.updated_at |> DateTime.truncate(:second) } @@ -279,7 +266,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do end # 要考虑是否有 comment_id 的情况,如果有,那么 就应该 load comment 而不是 article - defp parse_cited_content({"a", attrs, _}) do + defp parse_cited({"a", attrs, _}) do with {:ok, link} <- parse_link(attrs), true <- is_site_article_link?(link) do # IO.inspect(link, label: "parse link") From d5a0672e65fb20184929680f37184c23d542c9b0 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 15 Jun 2021 01:09:55 +0800 Subject: [PATCH 5/6] refactor(cite-task): fix citing self edge-case --- .../cms/delegates/cite_tasks.ex | 40 +++++++++---------- .../cms/cite_contents/cite_blog_test.exs | 18 +++++++++ .../cms/cite_contents/cite_job_test.exs | 18 +++++++++ .../cms/cite_contents/cite_post_test.exs | 19 +++++++++ 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/lib/groupher_server/cms/delegates/cite_tasks.ex b/lib/groupher_server/cms/delegates/cite_tasks.ex index a7c5002c1..6f249c7a2 100644 --- a/lib/groupher_server/cms/delegates/cite_tasks.ex +++ b/lib/groupher_server/cms/delegates/cite_tasks.ex @@ -76,8 +76,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do end end - # defp batch_done - + # batch insert CitedContent record and update citing count defp update_cited_info(cited_contents) do # see: https://github.com/elixir-ecto/ecto/issues/1932#issuecomment-314083252 clean_cited_contents = @@ -86,10 +85,9 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do |> Enum.map(&Map.delete(&1, :cited_content)) |> Enum.map(&Map.delete(&1, :citing_time)) - with true <- {0, nil} !== Repo.insert_all(CitedContent, clean_cited_contents) do - update_citing_count(cited_contents) - else - _ -> {:error, "insert cited content error"} + case {0, nil} !== Repo.insert_all(CitedContent, clean_cited_contents) do + true -> update_citing_count(cited_contents) + false -> {:error, "insert cited content error"} end end @@ -166,31 +164,31 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do do_parse_cited_info(content, block_id, links) end - defp do_parse_cited_info(%Comment{} = comment, block_id, links) do - # IO.inspect(links, label: "links -> ") - - Enum.reduce(links, [], fn link, acc -> - case parse_cited(link) do - {:ok, cited} -> List.insert_at(acc, 0, shape_cited(comment, cited, block_id)) - _ -> acc - end - end) - |> Enum.uniq() - end - # links Floki parsed fmt + # content means both article and comment # e.g: # [{"a", [{"href", "https://coderplanets.com/post/195675"}], []},] - defp do_parse_cited_info(article, block_id, links) do + defp do_parse_cited_info(content, block_id, links) do Enum.reduce(links, [], fn link, acc -> - case parse_cited(link) do - {:ok, cited} -> List.insert_at(acc, 0, shape_cited(article, cited, block_id)) + case parse_valid_cited(content.id, link) do + {:ok, cited} -> List.insert_at(acc, 0, shape_cited(content, cited, block_id)) _ -> acc end end) |> Enum.uniq() end + # parse cited with check if citing link is point to itself + defp parse_valid_cited(content_id, link) do + with {:ok, cited} <- parse_cited(link), + %{content: content} <- cited do + case content.id !== content_id do + true -> {:ok, cited} + false -> {:error, "citing itself"} + end + end + end + # cite article in comment # 在评论中引用文章 defp shape_cited(%Comment{} = comment, %{type: :article, content: cited}, block_id) do diff --git a/test/groupher_server/cms/cite_contents/cite_blog_test.exs b/test/groupher_server/cms/cite_contents/cite_blog_test.exs index c8dffaffa..31868364b 100644 --- a/test/groupher_server/cms/cite_contents/cite_blog_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_blog_test.exs @@ -73,6 +73,24 @@ defmodule GroupherServer.Test.CMS.CiteContent.Blog do assert blog.meta.citing_count == 0 end + @tag :wip + test "cited comment itself should not work", ~m(user blog)a do + {:ok, cited_comment} = CMS.create_comment(:blog, blog.id, mock_rich_text("hello"), user) + + {:ok, comment} = + CMS.update_comment( + cited_comment, + mock_comment( + ~s(the ) + ) + ) + + CiteTasks.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 0 + end + @tag :wip test "can cite blog's comment in blog", ~m(community user blog blog2 blog_attrs)a do {:ok, comment} = CMS.create_comment(:blog, blog.id, mock_rich_text("hello"), user) diff --git a/test/groupher_server/cms/cite_contents/cite_job_test.exs b/test/groupher_server/cms/cite_contents/cite_job_test.exs index 9bdeb8ffc..9e40ed24e 100644 --- a/test/groupher_server/cms/cite_contents/cite_job_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_job_test.exs @@ -73,6 +73,24 @@ defmodule GroupherServer.Test.CMS.CiteContent.Job do assert job.meta.citing_count == 0 end + @tag :wip + test "cited comment itself should not work", ~m(user job)a do + {:ok, cited_comment} = CMS.create_comment(:job, job.id, mock_rich_text("hello"), user) + + {:ok, comment} = + CMS.update_comment( + cited_comment, + mock_comment( + ~s(the ) + ) + ) + + CiteTasks.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 0 + end + @tag :wip test "can cite job's comment in job", ~m(community user job job2 job_attrs)a do {:ok, comment} = CMS.create_comment(:job, job.id, mock_rich_text("hello"), user) diff --git a/test/groupher_server/cms/cite_contents/cite_post_test.exs b/test/groupher_server/cms/cite_contents/cite_post_test.exs index 0f1d6674b..f5f71fcfb 100644 --- a/test/groupher_server/cms/cite_contents/cite_post_test.exs +++ b/test/groupher_server/cms/cite_contents/cite_post_test.exs @@ -62,6 +62,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Post do assert post5.meta.citing_count == 1 end + @tag :wip test "cited post itself should not work", ~m(user community post_attrs)a do {:ok, post} = CMS.create_article(community, :post, post_attrs, user) @@ -74,6 +75,24 @@ defmodule GroupherServer.Test.CMS.CiteContent.Post do assert post.meta.citing_count == 0 end + @tag :wip + test "cited comment itself should not work", ~m(user post)a do + {:ok, cited_comment} = CMS.create_comment(:post, post.id, mock_rich_text("hello"), user) + + {:ok, comment} = + CMS.update_comment( + cited_comment, + mock_comment( + ~s(the ) + ) + ) + + CiteTasks.handle(comment) + + {:ok, cited_comment} = ORM.find(Comment, cited_comment.id) + assert cited_comment.meta.citing_count == 0 + end + @tag :wip test "can cite post's comment in post", ~m(community user post post2 post_attrs)a do {:ok, comment} = CMS.create_comment(:post, post.id, mock_rich_text("hello"), user) From ba9ec83b2f4985dbeaec4b2d6502583b380bc922 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 15 Jun 2021 01:15:36 +0800 Subject: [PATCH 6/6] refactor(cite-task): fmt --- .../cms/delegates/cite_tasks.ex | 141 +++++++++--------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/lib/groupher_server/cms/delegates/cite_tasks.ex b/lib/groupher_server/cms/delegates/cite_tasks.ex index 6f249c7a2..f4dc99eb1 100644 --- a/lib/groupher_server/cms/delegates/cite_tasks.ex +++ b/lib/groupher_server/cms/delegates/cite_tasks.ex @@ -161,14 +161,14 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do defp parse_cited_info_per_block(content, %{"id" => block_id, "data" => %{"text" => text}}) do links = Floki.find(text, "a[href]") - do_parse_cited_info(content, block_id, links) + do_parse_cited_info_per_block(content, block_id, links) end # links Floki parsed fmt # content means both article and comment # e.g: # [{"a", [{"href", "https://coderplanets.com/post/195675"}], []},] - defp do_parse_cited_info(content, block_id, links) do + defp do_parse_cited_info_per_block(content, block_id, links) do Enum.reduce(links, [], fn link, acc -> case parse_valid_cited(content.id, link) do {:ok, cited} -> List.insert_at(acc, 0, shape_cited(content, cited, block_id)) @@ -189,6 +189,75 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do end end + # return fmt: %{type: :comment | :article, content: %Comment{} | Article} + # 要考虑是否有 comment_id 的情况,如果有,那么 就应该 load comment 而不是 article + defp parse_cited({"a", attrs, _}) do + with {:ok, link} <- parse_link(attrs), + true <- is_site_article_link?(link) do + # IO.inspect(link, label: "parse link") + # IO.inspect(is_comment_link?(link), label: "is_comment_link") + + case is_comment_link?(link) do + true -> load_cited_comment_from_url(link) + false -> load_cited_article_from_url(link) + end + end + end + + @doc """ + parse link from Floki parse result + + e.g: + [{"href", "https://coderplanets.com/post/190220", "bla", "bla"}] -> + {:ok, "https://coderplanets.com/post/190220"} + """ + defp parse_link(attrs) do + with {"href", link} <- Enum.find(attrs, fn {a, _v} -> a == "href" end) do + {:ok, link} + else + _ -> {:error, "invalid fmt"} + end + end + + # 检测是否是站内文章的链接 + defp is_site_article_link?(url) do + Enum.any?(@valid_article_prefix, &String.starts_with?(url, &1)) + end + + defp is_comment_link?(url) do + with %{query: query} <- URI.parse(url) do + not is_nil(query) and String.starts_with?(query, "comment_id=") + end + end + + defp load_cited_comment_from_url(url) do + %{query: query} = URI.parse(url) + + try do + comment_id = URI.decode_query(query) |> Map.get("comment_id") + + with {:ok, comment} <- ORM.find(Comment, comment_id) do + {:ok, %{type: :comment, content: comment}} + end + rescue + _ -> {:error, "load comment error"} + end + end + + # get cited article from url + # e.g: https://coderplanets.com/post/189993 -> ORM.find(Post, 189993) + defp load_cited_article_from_url(url) do + %{path: path} = URI.parse(url) + path_list = path |> String.split("/") + thread = path_list |> Enum.at(1) |> String.downcase() |> String.to_atom() + article_id = path_list |> Enum.at(2) + + with {:ok, info} <- match(thread), + {:ok, article} <- ORM.find(info.model, article_id) do + {:ok, %{type: :article, content: article}} + end + end + # cite article in comment # 在评论中引用文章 defp shape_cited(%Comment{} = comment, %{type: :article, content: cited}, block_id) do @@ -263,74 +332,6 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do |> Map.put(info.foreign_key, article.id) end - # 要考虑是否有 comment_id 的情况,如果有,那么 就应该 load comment 而不是 article - defp parse_cited({"a", attrs, _}) do - with {:ok, link} <- parse_link(attrs), - true <- is_site_article_link?(link) do - # IO.inspect(link, label: "parse link") - # IO.inspect(is_comment_link?(link), label: "is_comment_link") - - case is_comment_link?(link) do - true -> load_cited_comment_from_url(link) - false -> load_cited_article_from_url(link) - end - end - end - - @doc """ - parse link from Floki parse result - - e.g: - [{"href", "https://coderplanets.com/post/190220", "bla", "bla"}] -> - {:ok, "https://coderplanets.com/post/190220"} - """ - defp parse_link(attrs) do - with {"href", link} <- Enum.find(attrs, fn {a, _v} -> a == "href" end) do - {:ok, link} - else - _ -> {:error, "invalid fmt"} - end - end - - # 检测是否是站内文章的链接 - defp is_site_article_link?(url) do - Enum.any?(@valid_article_prefix, &String.starts_with?(url, &1)) - end - - defp is_comment_link?(url) do - with %{query: query} <- URI.parse(url) do - not is_nil(query) and String.starts_with?(query, "comment_id=") - end - end - - defp load_cited_comment_from_url(url) do - %{query: query} = URI.parse(url) - - try do - comment_id = URI.decode_query(query) |> Map.get("comment_id") - - with {:ok, comment} <- ORM.find(Comment, comment_id) do - {:ok, %{type: :comment, content: comment}} - end - rescue - _ -> {:error, "load comment error"} - end - end - - # get cited article from url - # e.g: https://coderplanets.com/post/189993 -> ORM.find(Post, 189993) - defp load_cited_article_from_url(url) do - %{path: path} = URI.parse(url) - path_list = path |> String.split("/") - thread = path_list |> Enum.at(1) |> String.downcase() |> String.to_atom() - article_id = path_list |> Enum.at(2) - - with {:ok, info} <- match(thread), - {:ok, article} <- ORM.find(info.model, article_id) do - {:ok, %{type: :article, content: article}} - end - end - defp result({:ok, %{update_cited_info: result}}), do: {:ok, result} defp result({:error, :update_cited_info, _result, _steps}) do