Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.

Commit 617cd3d

Browse files
authored
refactor(cite-task): support cite artilce in side comment (#399)
* refactor(cite-task): support cite artilce in side comment * refactor(cite-task): support cross comment citing * refactor(cite-task): add some inline doc * refactor(cite-task): fmt naming * refactor(cite-task): fix citing self edge-case * refactor(cite-task): fmt
1 parent e4e266b commit 617cd3d

File tree

13 files changed

+467
-72
lines changed

13 files changed

+467
-72
lines changed

lib/groupher_server/cms/delegates/article_curd.ex

+5-3
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,10 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
164164
|> Multi.run(:update_user_published_meta, fn _, _ ->
165165
Accounts.update_published_states(uid, thread)
166166
end)
167-
|> Multi.run(:block_tasks, fn _, %{create_article: article} ->
167+
|> Multi.run(:after_tasks, fn _, %{create_article: article} ->
168168
Later.run({CiteTasks, :handle, [article]})
169+
Later.run({__MODULE__, :notify_admin_new_article, [article]})
169170
end)
170-
# TODO: run mini tasks
171171
|> Multi.run(:mention_users, fn _, %{create_article: article} ->
172172
# article.body |> Jason.decode!() |> 各种小 task
173173
Delivery.mention_from_content(community.raw, thread, article, attrs, %User{id: uid})
@@ -223,6 +223,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
223223
|> Multi.run(:update_edit_status, fn _, %{update_article: update_article} ->
224224
ArticleCommunity.update_edit_status(update_article)
225225
end)
226+
|> Multi.run(:after_tasks, fn _, %{update_article: update_article} ->
227+
Later.run({CiteTasks, :handle, [update_article]})
228+
end)
226229
|> Repo.transaction()
227230
|> result()
228231
end
@@ -449,7 +452,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
449452

450453
# create done
451454
defp result({:ok, %{set_active_at_timestamp: result}}) do
452-
Later.run({__MODULE__, :notify_admin_new_article, [result]})
453455
{:ok, result}
454456
end
455457

lib/groupher_server/cms/delegates/cite_tasks.ex

+158-43
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
2929
import Helper.ErrorCode
3030

3131
alias GroupherServer.{CMS, Repo}
32-
alias CMS.Model.CitedContent
32+
alias CMS.Model.{CitedContent, Comment}
3333
alias Helper.ORM
3434

3535
alias Ecto.Multi
@@ -38,16 +38,16 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
3838
@article_threads get_config(:article, :threads)
3939
@valid_article_prefix Enum.map(@article_threads, &"#{@site_host}/#{&1}/")
4040

41-
def handle(%{body: body} = article) do
41+
def handle(%{body: body} = content) do
4242
with {:ok, %{"blocks" => blocks}} <- Jason.decode(body),
43-
article <- Repo.preload(article, author: :user) do
43+
content <- preload_content_author(content) do
4444
Multi.new()
4545
|> Multi.run(:delete_all_cited_contents, fn _, _ ->
46-
delete_all_cited_contents(article)
46+
delete_all_cited_contents(content)
4747
end)
4848
|> Multi.run(:update_cited_info, fn _, _ ->
4949
blocks
50-
|> Enum.reduce([], &(&2 ++ parse_cited_info_per_block(article, &1)))
50+
|> Enum.reduce([], &(&2 ++ parse_cited_info_per_block(content, &1)))
5151
|> merge_same_cited_article_block
5252
|> update_cited_info
5353
end)
@@ -56,9 +56,17 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
5656
end
5757
end
5858

59+
def preload_content_author(%Comment{} = comment), do: comment
60+
def preload_content_author(article), do: Repo.preload(article, author: :user)
61+
5962
# delete all records before insert_all, this will dynamiclly update
6063
# those cited info when update article
6164
# 插入引用记录之前先全部清除,这样可以在更新文章的时候自动计算引用信息
65+
defp delete_all_cited_contents(%Comment{} = comment) do
66+
query = from(c in CitedContent, where: c.comment_id == ^comment.id)
67+
ORM.delete_all(query, :if_exist)
68+
end
69+
6270
defp delete_all_cited_contents(article) do
6371
with {:ok, thread} <- thread_of_article(article),
6472
{:ok, info} <- match(thread) do
@@ -68,15 +76,18 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
6876
end
6977
end
7078

71-
# defp batch_done
72-
79+
# batch insert CitedContent record and update citing count
7380
defp update_cited_info(cited_contents) do
74-
clean_cited_contents = Enum.map(cited_contents, &Map.delete(&1, :cited_article))
75-
# IO.inspect(clean_cited_contents, label: "clean_cited_contents")
76-
with true <- {0, nil} !== Repo.insert_all(CitedContent, clean_cited_contents) do
77-
update_citing_count(cited_contents)
78-
else
79-
_ -> {:error, "insert cited content error"}
81+
# see: https://github.com/elixir-ecto/ecto/issues/1932#issuecomment-314083252
82+
clean_cited_contents =
83+
cited_contents
84+
|> Enum.map(&(&1 |> Map.merge(%{inserted_at: &1.citing_time, updated_at: &1.citing_time})))
85+
|> Enum.map(&Map.delete(&1, :cited_content))
86+
|> Enum.map(&Map.delete(&1, :citing_time))
87+
88+
case {0, nil} !== Repo.insert_all(CitedContent, clean_cited_contents) do
89+
true -> update_citing_count(cited_contents)
90+
false -> {:error, "insert cited content error"}
8091
end
8192
end
8293

@@ -85,10 +96,10 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
8596
count_query = from(c in CitedContent, where: c.cited_by_id == ^content.cited_by_id)
8697
count = Repo.aggregate(count_query, :count)
8798

88-
cited_article = content.cited_article
89-
meta = Map.merge(cited_article.meta, %{citing_count: count})
99+
cited_content = content.cited_content
100+
meta = Map.merge(cited_content.meta, %{citing_count: count})
90101

91-
case cited_article |> ORM.update_meta(meta) do
102+
case cited_content |> ORM.update_meta(meta) do
92103
{:ok, _} -> true
93104
{:error, _} -> false
94105
end
@@ -140,47 +151,56 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
140151
block_linker: ["block-ZgKJs"],
141152
cited_by_id: 190057,
142153
cited_by_type: "POST",
143-
cited_article: #loaded,
154+
cited_content: #loaded,
144155
post_id: 190059,
145156
user_id: 1413053
146157
}
147158
...
148159
]
149160
"""
150-
defp parse_cited_info_per_block(article, %{"id" => block_id, "data" => %{"text" => text}}) do
151-
links_in_block = Floki.find(text, "a[href]")
152-
153-
Enum.reduce(links_in_block, [], fn link, acc ->
154-
with {:ok, cited_article} <- parse_cited_article(link),
155-
# do not cite artilce itself
156-
true <- article.id !== cited_article.id do
157-
List.insert_at(acc, 0, shape_cited_content(article, cited_article, block_id))
158-
else
161+
defp parse_cited_info_per_block(content, %{"id" => block_id, "data" => %{"text" => text}}) do
162+
links = Floki.find(text, "a[href]")
163+
164+
do_parse_cited_info_per_block(content, block_id, links)
165+
end
166+
167+
# links Floki parsed fmt
168+
# content means both article and comment
169+
# e.g:
170+
# [{"a", [{"href", "https://coderplanets.com/post/195675"}], []},]
171+
defp do_parse_cited_info_per_block(content, block_id, links) do
172+
Enum.reduce(links, [], fn link, acc ->
173+
case parse_valid_cited(content.id, link) do
174+
{:ok, cited} -> List.insert_at(acc, 0, shape_cited(content, cited, block_id))
159175
_ -> acc
160176
end
161177
end)
162178
|> Enum.uniq()
163179
end
164180

165-
defp shape_cited_content(article, cited_article, block_id) do
166-
{:ok, thread} = thread_of_article(article)
167-
{:ok, info} = match(thread)
168-
169-
%{
170-
cited_by_id: cited_article.id,
171-
cited_by_type: cited_article.meta.thread,
172-
# used for updating citing_count, avoid load again
173-
cited_article: cited_article,
174-
block_linker: [block_id],
175-
user_id: article.author.user.id
176-
}
177-
|> Map.put(info.foreign_key, article.id)
181+
# parse cited with check if citing link is point to itself
182+
defp parse_valid_cited(content_id, link) do
183+
with {:ok, cited} <- parse_cited(link),
184+
%{content: content} <- cited do
185+
case content.id !== content_id do
186+
true -> {:ok, cited}
187+
false -> {:error, "citing itself"}
188+
end
189+
end
178190
end
179191

180-
defp parse_cited_article({"a", attrs, _}) do
192+
# return fmt: %{type: :comment | :article, content: %Comment{} | Article}
193+
# 要考虑是否有 comment_id 的情况,如果有,那么 就应该 load comment 而不是 article
194+
defp parse_cited({"a", attrs, _}) do
181195
with {:ok, link} <- parse_link(attrs),
182196
true <- is_site_article_link?(link) do
183-
load_cited_article_from_url(link)
197+
# IO.inspect(link, label: "parse link")
198+
# IO.inspect(is_comment_link?(link), label: "is_comment_link")
199+
200+
case is_comment_link?(link) do
201+
true -> load_cited_comment_from_url(link)
202+
false -> load_cited_article_from_url(link)
203+
end
184204
end
185205
end
186206

@@ -204,6 +224,26 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
204224
Enum.any?(@valid_article_prefix, &String.starts_with?(url, &1))
205225
end
206226

227+
defp is_comment_link?(url) do
228+
with %{query: query} <- URI.parse(url) do
229+
not is_nil(query) and String.starts_with?(query, "comment_id=")
230+
end
231+
end
232+
233+
defp load_cited_comment_from_url(url) do
234+
%{query: query} = URI.parse(url)
235+
236+
try do
237+
comment_id = URI.decode_query(query) |> Map.get("comment_id")
238+
239+
with {:ok, comment} <- ORM.find(Comment, comment_id) do
240+
{:ok, %{type: :comment, content: comment}}
241+
end
242+
rescue
243+
_ -> {:error, "load comment error"}
244+
end
245+
end
246+
207247
# get cited article from url
208248
# e.g: https://coderplanets.com/post/189993 -> ORM.find(Post, 189993)
209249
defp load_cited_article_from_url(url) do
@@ -212,11 +252,86 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
212252
thread = path_list |> Enum.at(1) |> String.downcase() |> String.to_atom()
213253
article_id = path_list |> Enum.at(2)
214254

215-
with {:ok, info} <- match(thread) do
216-
ORM.find(info.model, article_id)
255+
with {:ok, info} <- match(thread),
256+
{:ok, article} <- ORM.find(info.model, article_id) do
257+
{:ok, %{type: :article, content: article}}
217258
end
218259
end
219260

261+
# cite article in comment
262+
# 在评论中引用文章
263+
defp shape_cited(%Comment{} = comment, %{type: :article, content: cited}, block_id) do
264+
%{
265+
cited_by_id: cited.id,
266+
cited_by_type: cited.meta.thread,
267+
comment_id: comment.id,
268+
block_linker: [block_id],
269+
user_id: comment.author_id,
270+
# extra fields for next-step usage
271+
# used for updating citing_count, avoid load again
272+
cited_content: cited,
273+
# for later insert all
274+
citing_time: comment.updated_at |> DateTime.truncate(:second)
275+
}
276+
end
277+
278+
# cite comment in comment
279+
# 评论中引用评论
280+
defp shape_cited(%Comment{} = comment, %{type: :comment, content: cited}, block_id) do
281+
%{
282+
cited_by_id: cited.id,
283+
cited_by_type: "COMMENT",
284+
comment_id: comment.id,
285+
block_linker: [block_id],
286+
user_id: comment.author_id,
287+
# extra fields for next-step usage
288+
# used for updating citing_count, avoid load again
289+
cited_content: cited,
290+
# for later insert all
291+
citing_time: comment.updated_at |> DateTime.truncate(:second)
292+
}
293+
end
294+
295+
# cite article in article
296+
# 文章之间相互引用
297+
defp shape_cited(article, %{type: :article, content: cited}, block_id) do
298+
{:ok, thread} = thread_of_article(article)
299+
{:ok, info} = match(thread)
300+
301+
%{
302+
cited_by_id: cited.id,
303+
cited_by_type: cited.meta.thread,
304+
block_linker: [block_id],
305+
user_id: article.author.user.id,
306+
# extra fields for next-step usage
307+
# used for updating citing_count, avoid load again
308+
cited_content: cited,
309+
# for later insert all
310+
citing_time: article.updated_at |> DateTime.truncate(:second)
311+
}
312+
|> Map.put(info.foreign_key, article.id)
313+
end
314+
315+
# cite comment in article
316+
# 文章中引用评论
317+
defp shape_cited(article, %{type: :comment, content: cited}, block_id) do
318+
{:ok, thread} = thread_of_article(article)
319+
{:ok, info} = match(thread)
320+
321+
%{
322+
cited_by_id: cited.id,
323+
cited_by_type: "COMMENT",
324+
block_linker: [block_id],
325+
user_id: article.author.user.id,
326+
# extra fields for next-step usage
327+
# used for updating citing_count, avoid load again
328+
cited_content: cited,
329+
# for later insert all
330+
citing_time: article.updated_at |> DateTime.truncate(:second)
331+
}
332+
|> Map.put(info.foreign_key, article.id)
333+
end
334+
220335
defp result({:ok, %{update_cited_info: result}}), do: {:ok, result}
221336

222337
defp result({:error, :update_cited_info, _result, _steps}) do

lib/groupher_server/cms/delegates/comment_curd.ex

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule GroupherServer.CMS.Delegate.CommentCurd do
1111
import ShortMaps
1212

1313
alias Helper.Types, as: T
14-
alias Helper.{ORM, QueryBuilder, Converter}
14+
alias Helper.{Later, ORM, QueryBuilder, Converter}
1515
alias GroupherServer.{Accounts, CMS, Repo}
1616
alias CMS.Model.Post
1717

@@ -113,6 +113,9 @@ defmodule GroupherServer.CMS.Delegate.CommentCurd do
113113
false -> CMS.update_active_timestamp(thread, article)
114114
end
115115
end)
116+
|> Multi.run(:block_tasks, fn _, %{create_comment: create_comment} ->
117+
Later.run({CiteTasks, :handle, [create_comment]})
118+
end)
116119
|> Repo.transaction()
117120
|> result()
118121
else

lib/groupher_server/cms/models/cited_content.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defmodule GroupherServer.CMS.Model.CitedContent do
1313

1414
alias CMS.Model.Comment
1515

16-
@timestamps_opts [type: :utc_datetime_usec]
16+
@timestamps_opts [type: :utc_datetime]
1717

1818
@required_fields ~w(cited_by_type cited_by_id user_id)a
1919
@article_cast_fields general_article_fields(:cast)

lib/groupher_server/cms/models/embeds/comment_meta.ex

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule GroupherServer.CMS.Model.Embeds.CommentMeta do
77

88
import Ecto.Changeset
99

10-
@optional_fields ~w(is_article_author_upvoted report_count is_reply_to_others reported_count reported_user_ids)a
10+
@optional_fields ~w(is_article_author_upvoted report_count is_reply_to_others reported_count reported_user_ids citing_count)a
1111

1212
@doc "for test usage"
1313
def default_meta() do
@@ -17,7 +17,8 @@ defmodule GroupherServer.CMS.Model.Embeds.CommentMeta do
1717
report_count: 0,
1818
upvoted_user_ids: [],
1919
reported_user_ids: [],
20-
reported_count: 0
20+
reported_count: 0,
21+
citing_count: 0
2122
}
2223
end
2324

@@ -31,6 +32,7 @@ defmodule GroupherServer.CMS.Model.Embeds.CommentMeta do
3132
field(:upvoted_user_ids, {:array, :integer}, default: [])
3233
field(:reported_user_ids, {:array, :integer}, default: [])
3334
field(:reported_count, :integer, default: 0)
35+
field(:citing_count, :integer, default: 0)
3436
end
3537

3638
def changeset(struct, params) do
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule GroupherServer.Repo.Migrations.MissingTimestampForCiteContents do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:cited_contents) do
6+
timestamps()
7+
end
8+
end
9+
end

0 commit comments

Comments
 (0)