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

Commit e97cce1

Browse files
committed
refactor(cite-task): support cross comment citing
1 parent 52934d4 commit e97cce1

File tree

7 files changed

+253
-37
lines changed

7 files changed

+253
-37
lines changed

lib/groupher_server/cms/delegates/cite_tasks.ex

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
7979
# defp batch_done
8080

8181
defp update_cited_info(cited_contents) do
82-
clean_cited_contents = Enum.map(cited_contents, &Map.delete(&1, :cited_article))
83-
# IO.inspect(clean_cited_contents, label: "clean_cited_contents")
84-
# IO.inspect(cited_contents, label: "cited_contents")
82+
# see: https://github.com/elixir-ecto/ecto/issues/1932#issuecomment-314083252
83+
clean_cited_contents =
84+
cited_contents
85+
|> Enum.map(&(&1 |> Map.merge(%{inserted_at: &1.citing_time, updated_at: &1.citing_time})))
86+
|> Enum.map(&Map.delete(&1, :cited_content))
87+
|> Enum.map(&Map.delete(&1, :citing_time))
8588

8689
with true <- {0, nil} !== Repo.insert_all(CitedContent, clean_cited_contents) do
8790
update_citing_count(cited_contents)
@@ -95,10 +98,10 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
9598
count_query = from(c in CitedContent, where: c.cited_by_id == ^content.cited_by_id)
9699
count = Repo.aggregate(count_query, :count)
97100

98-
cited_article = content.cited_article
99-
meta = Map.merge(cited_article.meta, %{citing_count: count})
101+
cited_content = content.cited_content
102+
meta = Map.merge(cited_content.meta, %{citing_count: count})
100103

101-
case cited_article |> ORM.update_meta(meta) do
104+
case cited_content |> ORM.update_meta(meta) do
102105
{:ok, _} -> true
103106
{:error, _} -> false
104107
end
@@ -150,23 +153,26 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
150153
block_linker: ["block-ZgKJs"],
151154
cited_by_id: 190057,
152155
cited_by_type: "POST",
153-
cited_article: #loaded,
156+
cited_content: #loaded,
154157
post_id: 190059,
155158
user_id: 1413053
156159
}
157160
...
158161
]
159162
"""
160-
defp parse_cited_info_per_block(article, %{"id" => block_id, "data" => %{"text" => text}}) do
163+
defp parse_cited_info_per_block(content, %{"id" => block_id, "data" => %{"text" => text}}) do
161164
links = Floki.find(text, "a[href]")
162165

163-
do_parse_cited_info(article, block_id, links)
166+
do_parse_cited_info(content, block_id, links)
164167
end
165168

166169
defp do_parse_cited_info(%Comment{} = comment, block_id, links) do
170+
# IO.inspect(links, label: "links -> ")
171+
167172
Enum.reduce(links, [], fn link, acc ->
168-
with {:ok, cited_article} <- parse_cited_article(link) do
169-
List.insert_at(acc, 0, shape_cited_content(comment, cited_article, block_id))
173+
with {:ok, cited_content} <- parse_cited_content(link) do
174+
# IO.inspect(cited_content, label: "before shape cited_content")
175+
List.insert_at(acc, 0, shape_cited_content(comment, cited_content, block_id))
170176
else
171177
_ -> acc
172178
end
@@ -179,50 +185,102 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
179185
# [{"a", [{"href", "https://coderplanets.com/post/195675"}], []},]
180186
defp do_parse_cited_info(article, block_id, links) do
181187
Enum.reduce(links, [], fn link, acc ->
182-
with {:ok, cited_article} <- parse_cited_article(link),
183-
# do not cite artilce itself
184-
true <- article.id !== cited_article.id do
185-
List.insert_at(acc, 0, shape_cited_content(article, cited_article, block_id))
188+
with {:ok, cited_content} <- parse_cited_content(link) do
189+
# do not cite artilce itself
190+
# true <- article.id !== cited_content.id do
191+
List.insert_at(acc, 0, shape_cited_content(article, cited_content, block_id))
186192
else
187193
_ -> acc
188194
end
189195
end)
190196
|> Enum.uniq()
191197
end
192198

193-
# 在评论中引用文章的情况
194-
defp shape_cited_content(%Comment{} = comment, cited_article, block_id) do
199+
# 在评论中引用文章
200+
defp shape_cited_content(
201+
%Comment{} = comment,
202+
%{type: :article, content: cited_article},
203+
block_id
204+
) do
195205
%{
196206
cited_by_id: cited_article.id,
197207
cited_by_type: cited_article.meta.thread,
208+
comment_id: comment.id,
209+
block_linker: [block_id],
210+
user_id: comment.author_id,
198211
# used for updating citing_count, avoid load again
199-
cited_article: cited_article,
212+
cited_content: cited_article,
213+
# for later insert all
214+
citing_time: comment.updated_at |> DateTime.truncate(:second)
215+
}
216+
end
217+
218+
# 评论中引用评论
219+
defp shape_cited_content(
220+
%Comment{} = comment,
221+
%{type: :comment, content: cited_comment},
222+
block_id
223+
) do
224+
%{
225+
cited_by_id: cited_comment.id,
226+
cited_by_type: "COMMENT",
200227
comment_id: comment.id,
201228
block_linker: [block_id],
202-
user_id: comment.author_id
229+
user_id: comment.author_id,
230+
# used for updating citing_count, avoid load again
231+
cited_content: cited_comment,
232+
# for later insert all
233+
citing_time: comment.updated_at |> DateTime.truncate(:second)
203234
}
204235
end
205236

206-
# 文章之间相互引用的情况
207-
defp shape_cited_content(article, cited_article, block_id) do
237+
# 文章之间相互引用
238+
defp shape_cited_content(article, %{type: :article, content: cited_article}, block_id) do
208239
{:ok, thread} = thread_of_article(article)
209240
{:ok, info} = match(thread)
210241

211242
%{
212243
cited_by_id: cited_article.id,
213244
cited_by_type: cited_article.meta.thread,
245+
block_linker: [block_id],
246+
user_id: article.author.user.id,
214247
# used for updating citing_count, avoid load again
215-
cited_article: cited_article,
248+
cited_content: cited_article,
249+
# for later insert all
250+
citing_time: article.updated_at |> DateTime.truncate(:second)
251+
}
252+
|> Map.put(info.foreign_key, article.id)
253+
end
254+
255+
# 文章里引用评论
256+
defp shape_cited_content(article, %{type: :comment, content: cited_comment}, block_id) do
257+
{:ok, thread} = thread_of_article(article)
258+
{:ok, info} = match(thread)
259+
260+
%{
261+
cited_by_id: cited_comment.id,
262+
cited_by_type: "COMMENT",
216263
block_linker: [block_id],
217-
user_id: article.author.user.id
264+
user_id: article.author.user.id,
265+
# used for updating citing_count, avoid load again
266+
cited_content: cited_comment,
267+
# for later insert all
268+
citing_time: article.updated_at |> DateTime.truncate(:second)
218269
}
219270
|> Map.put(info.foreign_key, article.id)
220271
end
221272

222-
defp parse_cited_article({"a", attrs, _}) do
273+
# 要考虑是否有 comment_id 的情况,如果有,那么 就应该 load comment 而不是 article
274+
defp parse_cited_content({"a", attrs, _}) do
223275
with {:ok, link} <- parse_link(attrs),
224276
true <- is_site_article_link?(link) do
225-
load_cited_article_from_url(link)
277+
# IO.inspect(link, label: "parse link")
278+
# IO.inspect(is_comment_link?(link), label: "is_comment_link")
279+
280+
case is_comment_link?(link) do
281+
true -> load_cited_comment_from_url(link)
282+
false -> load_cited_article_from_url(link)
283+
end
226284
end
227285
end
228286

@@ -246,6 +304,32 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
246304
Enum.any?(@valid_article_prefix, &String.starts_with?(url, &1))
247305
end
248306

307+
defp is_comment_link?(url) do
308+
with %{query: query} <- URI.parse(url) do
309+
not is_nil(query) and String.starts_with?(query, "comment_id=")
310+
# is_valid_query = not is_nil(query) and String.starts_with?(query, "comment_id=")
311+
# comment_id = URI.decode_query(query) |> Map.get("comment_id")
312+
# query_not_empty = comment_id
313+
314+
# URI.decode_query(query) |> String.starts_with?("comment_id=")
315+
# IO.inspect(query, label: "the query")
316+
end
317+
end
318+
319+
defp load_cited_comment_from_url(url) do
320+
%{query: query} = URI.parse(url)
321+
322+
try do
323+
comment_id = URI.decode_query(query) |> Map.get("comment_id")
324+
325+
with {:ok, comment} <- ORM.find(Comment, comment_id) do
326+
{:ok, %{type: :comment, content: comment}}
327+
end
328+
rescue
329+
_ -> {:error, "load comment error"}
330+
end
331+
end
332+
249333
# get cited article from url
250334
# e.g: https://coderplanets.com/post/189993 -> ORM.find(Post, 189993)
251335
defp load_cited_article_from_url(url) do
@@ -254,8 +338,9 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
254338
thread = path_list |> Enum.at(1) |> String.downcase() |> String.to_atom()
255339
article_id = path_list |> Enum.at(2)
256340

257-
with {:ok, info} <- match(thread) do
258-
ORM.find(info.model, article_id)
341+
with {:ok, info} <- match(thread),
342+
{:ok, article} <- ORM.find(info.model, article_id) do
343+
{:ok, %{type: :article, content: article}}
259344
end
260345
end
261346

lib/groupher_server/cms/models/cited_content.ex

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 2 deletions
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
Lines changed: 9 additions & 0 deletions
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

test/groupher_server/cms/cite_contents/cite_blog_test.exs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Blog do
66
alias Helper.ORM
77
alias GroupherServer.CMS
88

9-
alias CMS.Model.Blog
10-
9+
alias CMS.Model.{Blog, Comment, CitedContent}
1110
alias CMS.Delegate.CiteTasks
1211

1312
@site_host get_config(:general, :site_host)
@@ -75,6 +74,47 @@ defmodule GroupherServer.Test.CMS.CiteContent.Blog do
7574
end
7675

7776
@tag :wip
77+
test "can cite blog's comment in blog", ~m(community user blog blog2 blog_attrs)a do
78+
{:ok, comment} = CMS.create_comment(:blog, blog.id, mock_rich_text("hello"), user)
79+
80+
body =
81+
mock_rich_text(~s(the <a href=#{@site_host}/blog/#{blog2.id}?comment_id=#{comment.id} />))
82+
83+
blog_attrs = blog_attrs |> Map.merge(%{body: body})
84+
85+
{:ok, blog} = CMS.create_article(community, :blog, blog_attrs, user)
86+
CiteTasks.handle(blog)
87+
88+
{:ok, comment} = ORM.find(Comment, comment.id)
89+
assert comment.meta.citing_count == 1
90+
91+
{:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: comment.id})
92+
assert blog.id == cite_content.blog_id
93+
assert cite_content.cited_by_type == "COMMENT"
94+
end
95+
96+
@tag :wip
97+
test "can cite a comment in a comment", ~m(user blog)a do
98+
{:ok, cited_comment} = CMS.create_comment(:blog, blog.id, mock_rich_text("hello"), user)
99+
100+
comment_body =
101+
mock_rich_text(
102+
~s(the <a href=#{@site_host}/blog/#{blog.id}?comment_id=#{cited_comment.id} />)
103+
)
104+
105+
{:ok, comment} = CMS.create_comment(:blog, blog.id, comment_body, user)
106+
107+
CiteTasks.handle(comment)
108+
109+
{:ok, cited_comment} = ORM.find(Comment, cited_comment.id)
110+
assert cited_comment.meta.citing_count == 1
111+
112+
{:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: cited_comment.id})
113+
assert comment.id == cite_content.comment_id
114+
assert cited_comment.id == cite_content.cited_by_id
115+
assert cite_content.cited_by_type == "COMMENT"
116+
end
117+
78118
test "can cited blog inside a comment", ~m(user blog blog2 blog3 blog4 blog5)a do
79119
comment_body =
80120
mock_rich_text(

test/groupher_server/cms/cite_contents/cite_job_test.exs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ defmodule GroupherServer.Test.CMS.CiteContent.Job do
66
alias Helper.ORM
77
alias GroupherServer.CMS
88

9-
alias CMS.Model.Job
10-
9+
alias CMS.Model.{Job, Comment, CitedContent}
1110
alias CMS.Delegate.CiteTasks
1211

1312
@site_host get_config(:general, :site_host)
@@ -75,6 +74,47 @@ defmodule GroupherServer.Test.CMS.CiteContent.Job do
7574
end
7675

7776
@tag :wip
77+
test "can cite job's comment in job", ~m(community user job job2 job_attrs)a do
78+
{:ok, comment} = CMS.create_comment(:job, job.id, mock_rich_text("hello"), user)
79+
80+
body =
81+
mock_rich_text(~s(the <a href=#{@site_host}/job/#{job2.id}?comment_id=#{comment.id} />))
82+
83+
job_attrs = job_attrs |> Map.merge(%{body: body})
84+
85+
{:ok, job} = CMS.create_article(community, :job, job_attrs, user)
86+
CiteTasks.handle(job)
87+
88+
{:ok, comment} = ORM.find(Comment, comment.id)
89+
assert comment.meta.citing_count == 1
90+
91+
{:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: comment.id})
92+
assert job.id == cite_content.job_id
93+
assert cite_content.cited_by_type == "COMMENT"
94+
end
95+
96+
@tag :wip
97+
test "can cite a comment in a comment", ~m(user job)a do
98+
{:ok, cited_comment} = CMS.create_comment(:job, job.id, mock_rich_text("hello"), user)
99+
100+
comment_body =
101+
mock_rich_text(
102+
~s(the <a href=#{@site_host}/job/#{job.id}?comment_id=#{cited_comment.id} />)
103+
)
104+
105+
{:ok, comment} = CMS.create_comment(:job, job.id, comment_body, user)
106+
107+
CiteTasks.handle(comment)
108+
109+
{:ok, cited_comment} = ORM.find(Comment, cited_comment.id)
110+
assert cited_comment.meta.citing_count == 1
111+
112+
{:ok, cite_content} = ORM.find_by(CitedContent, %{cited_by_id: cited_comment.id})
113+
assert comment.id == cite_content.comment_id
114+
assert cited_comment.id == cite_content.cited_by_id
115+
assert cite_content.cited_by_type == "COMMENT"
116+
end
117+
78118
test "can cited job inside a comment", ~m(user job job2 job3 job4 job5)a do
79119
comment_body =
80120
mock_rich_text(

0 commit comments

Comments
 (0)