@@ -29,7 +29,7 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
29
29
import Helper.ErrorCode
30
30
31
31
alias GroupherServer . { CMS , Repo }
32
- alias CMS.Model.CitedContent
32
+ alias CMS.Model . { CitedContent , Comment }
33
33
alias Helper.ORM
34
34
35
35
alias Ecto.Multi
@@ -38,16 +38,16 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
38
38
@ article_threads get_config ( :article , :threads )
39
39
@ valid_article_prefix Enum . map ( @ article_threads , & "#{ @ site_host } /#{ & 1 } /" )
40
40
41
- def handle ( % { body: body } = article ) do
41
+ def handle ( % { body: body } = content ) do
42
42
with { :ok , % { "blocks" => blocks } } <- Jason . decode ( body ) ,
43
- article <- Repo . preload ( article , author: :user ) do
43
+ content <- preload_content_author ( content ) do
44
44
Multi . new ( )
45
45
|> Multi . run ( :delete_all_cited_contents , fn _ , _ ->
46
- delete_all_cited_contents ( article )
46
+ delete_all_cited_contents ( content )
47
47
end )
48
48
|> Multi . run ( :update_cited_info , fn _ , _ ->
49
49
blocks
50
- |> Enum . reduce ( [ ] , & ( & 2 ++ parse_cited_info_per_block ( article , & 1 ) ) )
50
+ |> Enum . reduce ( [ ] , & ( & 2 ++ parse_cited_info_per_block ( content , & 1 ) ) )
51
51
|> merge_same_cited_article_block
52
52
|> update_cited_info
53
53
end )
@@ -56,9 +56,17 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
56
56
end
57
57
end
58
58
59
+ def preload_content_author ( % Comment { } = comment ) , do: comment
60
+ def preload_content_author ( article ) , do: Repo . preload ( article , author: :user )
61
+
59
62
# delete all records before insert_all, this will dynamiclly update
60
63
# those cited info when update article
61
64
# 插入引用记录之前先全部清除,这样可以在更新文章的时候自动计算引用信息
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
+
62
70
defp delete_all_cited_contents ( article ) do
63
71
with { :ok , thread } <- thread_of_article ( article ) ,
64
72
{ :ok , info } <- match ( thread ) do
@@ -68,15 +76,18 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
68
76
end
69
77
end
70
78
71
- # defp batch_done
72
-
79
+ # batch insert CitedContent record and update citing count
73
80
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" }
80
91
end
81
92
end
82
93
@@ -85,10 +96,10 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
85
96
count_query = from ( c in CitedContent , where: c . cited_by_id == ^ content . cited_by_id )
86
97
count = Repo . aggregate ( count_query , :count )
87
98
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 } )
90
101
91
- case cited_article |> ORM . update_meta ( meta ) do
102
+ case cited_content |> ORM . update_meta ( meta ) do
92
103
{ :ok , _ } -> true
93
104
{ :error , _ } -> false
94
105
end
@@ -140,47 +151,56 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
140
151
block_linker: ["block-ZgKJs"],
141
152
cited_by_id: 190057,
142
153
cited_by_type: "POST",
143
- cited_article : #loaded,
154
+ cited_content : #loaded,
144
155
post_id: 190059,
145
156
user_id: 1413053
146
157
}
147
158
...
148
159
]
149
160
"""
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 ) )
159
175
_ -> acc
160
176
end
161
177
end )
162
178
|> Enum . uniq ( )
163
179
end
164
180
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
178
190
end
179
191
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
181
195
with { :ok , link } <- parse_link ( attrs ) ,
182
196
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
184
204
end
185
205
end
186
206
@@ -204,6 +224,26 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
204
224
Enum . any? ( @ valid_article_prefix , & String . starts_with? ( url , & 1 ) )
205
225
end
206
226
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
+
207
247
# get cited article from url
208
248
# e.g: https://coderplanets.com/post/189993 -> ORM.find(Post, 189993)
209
249
defp load_cited_article_from_url ( url ) do
@@ -212,11 +252,86 @@ defmodule GroupherServer.CMS.Delegate.CiteTasks do
212
252
thread = path_list |> Enum . at ( 1 ) |> String . downcase ( ) |> String . to_atom ( )
213
253
article_id = path_list |> Enum . at ( 2 )
214
254
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 } }
217
258
end
218
259
end
219
260
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
+
220
335
defp result ( { :ok , % { update_cited_info: result } } ) , do: { :ok , result }
221
336
222
337
defp result ( { :error , :update_cited_info , _result , _steps } ) do
0 commit comments