@@ -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
0 commit comments