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

Commit 22f2b6f

Browse files
authored
feat: article document concept (#409)
* chore: setup basic table * chore: basic create update remove workflow * chore: re-org read & paged without body field * chore: fix Repo create edege case * chore: fix body arg choas * fix: document issue in hooks * fix: document issue in create * fix: document issue in create * fix: missing paged blogs tests * fix: document return issue * fix: clean up * fix: clean up * fix: thread document test adjust
1 parent df4a280 commit 22f2b6f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1312
-154
lines changed

config/config.exs

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ config :groupher_server, :customization,
6666
sidebar_communities_index: %{}
6767

6868
config :groupher_server, :article,
69+
min_length: 10,
70+
max_length: 20_000,
6971
# NOTE: do not change unless you know what you are doing
7072
threads: [:post, :job, :repo, :blog],
7173
# in this period, paged articles will sort front if non-article-author commented

lib/groupher_server/cms/cms.ex

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ defmodule GroupherServer.CMS do
9292

9393
defdelegate mark_delete_article(thread, id), to: ArticleCURD
9494
defdelegate undo_mark_delete_article(thread, id), to: ArticleCURD
95+
defdelegate remove_article(thread, id), to: ArticleCURD
96+
defdelegate remove_article(thread, id, reason), to: ArticleCURD
9597

9698
defdelegate update_active_timestamp(thread, article), to: ArticleCURD
9799
defdelegate sink_article(thread, id), to: ArticleCURD

lib/groupher_server/cms/delegates/article_curd.ex

+63-23
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
1818

1919
alias Accounts.Model.User
2020
alias CMS.Model.{Author, Community, PinnedArticle, Embeds}
21+
alias CMS.Model.Repo, as: CMSRepo
2122

2223
alias CMS.Delegate.{
2324
ArticleCommunity,
2425
CommentCurd,
2526
ArticleTag,
2627
CommunityCURD,
28+
Document,
2729
Hooks
2830
}
2931

@@ -32,6 +34,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
3234
@active_period get_config(:article, :active_period_days)
3335
@default_emotions Embeds.ArticleEmotion.default_emotions()
3436
@default_article_meta Embeds.ArticleMeta.default_meta()
37+
@remove_article_hint "The content does not comply with the community norms"
3538

3639
@doc """
3740
read articles for un-logined user
@@ -40,7 +43,10 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
4043
with {:ok, info} <- match(thread) do
4144
Multi.new()
4245
|> Multi.run(:inc_views, fn _, _ -> ORM.read(info.model, id, inc: :views) end)
43-
|> Multi.run(:update_article_meta, fn _, %{inc_views: article} ->
46+
|> Multi.run(:load_html, fn _, %{inc_views: article} ->
47+
article |> Repo.preload(:document) |> done
48+
end)
49+
|> Multi.run(:update_article_meta, fn _, %{load_html: article} ->
4450
article_meta = ensure(article.meta, @default_article_meta)
4551
meta = Map.merge(article_meta, %{can_undo_sink: in_active_period?(thread, article)})
4652

@@ -57,17 +63,11 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
5763
def read_article(thread, id, %User{id: user_id}) do
5864
with {:ok, info} <- match(thread) do
5965
Multi.new()
60-
|> Multi.run(:inc_views, fn _, _ -> ORM.read(info.model, id, inc: :views) end)
61-
|> Multi.run(:update_article_meta, fn _, %{inc_views: article} ->
62-
article_meta = ensure(article.meta, @default_article_meta)
63-
meta = Map.merge(article_meta, %{can_undo_sink: in_active_period?(thread, article)})
64-
65-
ORM.update_meta(article, meta)
66-
end)
67-
|> Multi.run(:add_viewed_user, fn _, %{inc_views: article} ->
66+
|> Multi.run(:normal_read, fn _, _ -> read_article(thread, id) end)
67+
|> Multi.run(:add_viewed_user, fn _, %{normal_read: article} ->
6868
update_viewed_user_list(article, user_id)
6969
end)
70-
|> Multi.run(:set_viewer_has_states, fn _, %{inc_views: article} ->
70+
|> Multi.run(:set_viewer_has_states, fn _, %{normal_read: article} ->
7171
article_meta = if is_nil(article.meta), do: @default_article_meta, else: article.meta
7272

7373
viewer_has_states = %{
@@ -76,7 +76,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
7676
viewer_has_reported: user_id in article_meta.reported_user_ids
7777
}
7878

79-
{:ok, Map.merge(article, viewer_has_states)}
79+
article |> Map.merge(viewer_has_states) |> done
8080
end)
8181
|> Repo.transaction()
8282
|> result()
@@ -156,6 +156,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
156156
|> Multi.run(:create_article, fn _, _ ->
157157
do_create_article(info.model, attrs, author, community)
158158
end)
159+
|> Multi.run(:create_document, fn _, %{create_article: article} ->
160+
Document.create(article, attrs)
161+
end)
159162
|> Multi.run(:mirror_article, fn _, %{create_article: article} ->
160163
ArticleCommunity.mirror_article(thread, article.id, community.id)
161164
end)
@@ -211,14 +214,17 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
211214
@doc """
212215
update a article(post/job ...)
213216
"""
214-
def update_article(article, args) do
217+
def update_article(article, attrs) do
215218
Multi.new()
216219
|> Multi.run(:update_article, fn _, _ ->
217-
do_update_article(article, args)
220+
do_update_article(article, attrs)
221+
end)
222+
|> Multi.run(:update_document, fn _, %{update_article: update_article} ->
223+
Document.update(update_article, attrs)
218224
end)
219225
|> Multi.run(:update_comment_question_flag_if_need, fn _, %{update_article: update_article} ->
220226
# 如果帖子的类型变了,那么 update 所有的 flag
221-
case Map.has_key?(args, :is_question) do
227+
case Map.has_key?(attrs, :is_question) do
222228
true -> CommentCurd.batch_update_question_flag(update_article)
223229
false -> {:ok, :pass}
224230
end
@@ -319,6 +325,31 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
319325
end
320326
end
321327

328+
@doc """
329+
remove article forever
330+
"""
331+
def remove_article(thread, id, reason \\ @remove_article_hint) do
332+
with {:ok, info} <- match(thread),
333+
{:ok, article} <- ORM.find(info.model, id, preload: [:communities, [author: :user]]) do
334+
Multi.new()
335+
|> Multi.run(:remove_article, fn _, _ ->
336+
article |> ORM.delete()
337+
end)
338+
|> Multi.run(:update_community_article_count, fn _, _ ->
339+
CommunityCURD.update_community_count_field(article.communities, thread)
340+
end)
341+
|> Multi.run(:update_user_published_meta, fn _, _ ->
342+
Accounts.update_published_states(article.author.user.id, thread)
343+
end)
344+
|> Multi.run(:delete_document, fn _, _ ->
345+
Document.remove(thread, id)
346+
end)
347+
# TODO: notify author
348+
|> Repo.transaction()
349+
|> result()
350+
end
351+
end
352+
322353
@spec ensure_author_exists(User.t()) :: {:ok, User.t()}
323354
def ensure_author_exists(%User{} = user) do
324355
# unique_constraint: avoid race conditions, make sure user_id unique
@@ -392,13 +423,12 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
392423
end
393424

394425
# for create artilce step in Multi.new
395-
defp do_create_article(model, attrs, %Author{id: author_id}, %Community{id: community_id}) do
396-
# special article like Repo do not have :body, assign it with default-empty rich text
397-
body = Map.get(attrs, :body, Converter.Article.default_rich_text())
426+
defp do_create_article(model, %{body: _body} = attrs, %Author{id: author_id}, %Community{
427+
id: community_id
428+
}) do
398429
meta = @default_article_meta |> Map.merge(%{thread: module_to_upcase(model)})
399-
attrs = attrs |> Map.merge(%{body: body})
400430

401-
with {:ok, attrs} <- add_rich_text_attrs(attrs) do
431+
with {:ok, attrs} <- add_digest_attrs(attrs) do
402432
model.__struct__
403433
|> model.changeset(attrs)
404434
|> Ecto.Changeset.put_change(:emotions, @default_emotions)
@@ -409,26 +439,35 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
409439
end
410440
end
411441

442+
# Github Repo 没有传统的 body, 需要特殊处理
443+
# 赋值一个空的 body, 后续在 document 中处理
444+
# 注意:digest 那里也要特殊处理
445+
defp do_create_article(CMSRepo, attrs, author, community) do
446+
body = Map.get(attrs, :body, Converter.Article.default_rich_text())
447+
attrs = Map.put(attrs, :body, body)
448+
449+
do_create_article(CMSRepo, attrs, author, community)
450+
end
451+
412452
defp do_update_article(article, %{body: _} = attrs) do
413-
with {:ok, attrs} <- add_rich_text_attrs(attrs) do
453+
with {:ok, attrs} <- add_digest_attrs(attrs) do
414454
ORM.update(article, attrs)
415455
end
416456
end
417457

418458
defp do_update_article(article, attrs), do: ORM.update(article, attrs)
419459

420460
# is update or create article with body field, parsed and extand it into attrs
421-
defp add_rich_text_attrs(%{body: body} = attrs) when not is_nil(body) do
461+
defp add_digest_attrs(%{body: body} = attrs) when not is_nil(body) do
422462
with {:ok, parsed} <- Converter.Article.parse_body(body),
423463
{:ok, digest} <- Converter.Article.parse_digest(parsed.body_map) do
424464
attrs
425-
|> Map.merge(Map.take(parsed, [:body, :body_html]))
426465
|> Map.merge(%{digest: digest})
427466
|> done
428467
end
429468
end
430469

431-
defp add_rich_text_attrs(attrs), do: attrs
470+
defp add_digest_attrs(attrs), do: attrs
432471

433472
defp update_viewed_user_list(%{meta: nil} = article, user_id) do
434473
new_ids = Enum.uniq([user_id] ++ @default_article_meta.viewed_user_ids)
@@ -458,6 +497,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
458497

459498
defp result({:ok, %{update_edit_status: result}}), do: {:ok, result}
460499
defp result({:ok, %{update_article: result}}), do: {:ok, result}
500+
defp result({:ok, %{remove_article: result}}), do: {:ok, result}
461501
# NOTE: for read article, order is import
462502
defp result({:ok, %{set_viewer_has_states: result}}), do: result |> done()
463503
defp result({:ok, %{update_article_meta: result}}), do: {:ok, result}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
defmodule GroupherServer.CMS.Delegate.Document do
2+
@moduledoc """
3+
CURD operation on post/job ...
4+
"""
5+
import Ecto.Query, warn: false
6+
import Helper.Utils, only: [done: 1, thread_of_article: 2, get_config: 2]
7+
8+
import Helper.ErrorCode
9+
import ShortMaps
10+
11+
alias Helper.{ORM, Converter}
12+
alias GroupherServer.{CMS, Repo}
13+
14+
alias CMS.Model.ArticleDocument
15+
alias Ecto.Multi
16+
17+
# alias Helper.Converter.MdToEditor
18+
alias GroupherServer.Support.Factory
19+
20+
# TODO: spec repo logic
21+
def create(article, %{readme: readme} = attrs) do
22+
# .parse(markdown)
23+
# body = MdToEditor.mock_rich_text(readme)
24+
body = Factory.mock_rich_text(readme)
25+
attrs = attrs |> Map.drop([:readme]) |> Map.put(:body, body)
26+
create(article, attrs)
27+
end
28+
29+
# for create artilce step in Multi.new
30+
def create(article, %{body: body} = attrs) do
31+
with {:ok, article_thread} <- thread_of_article(article, :upcase),
32+
{:ok, parsed} <- Converter.Article.parse_body(body) do
33+
attrs = Map.take(parsed, [:body, :body_html])
34+
35+
Multi.new()
36+
|> Multi.run(:create_article_document, fn _, _ ->
37+
document_attrs =
38+
Map.merge(attrs, %{
39+
thread: article_thread,
40+
article_id: article.id,
41+
title: article.title
42+
})
43+
44+
ArticleDocument |> ORM.create(document_attrs)
45+
end)
46+
|> Multi.run(:create_thread_document, fn _, _ ->
47+
attrs = attrs |> Map.put(foreign_key(article_thread), article.id)
48+
49+
CMS.Model
50+
|> Module.concat("#{Recase.to_title(article_thread)}Document")
51+
|> ORM.create(attrs)
52+
end)
53+
|> Repo.transaction()
54+
|> result()
55+
end
56+
end
57+
58+
@doc """
59+
update both article and thread document
60+
"""
61+
def update(article, %{body: body} = attrs) when not is_nil(body) do
62+
with {:ok, article_thread} <- thread_of_article(article, :upcase),
63+
{:ok, article_doc} <- find_article_document(article_thread, article),
64+
{:ok, thread_doc} <- find_thread_document(article_thread, article),
65+
{:ok, parsed} <- Converter.Article.parse_body(body) do
66+
attrs = Map.take(parsed, [:body, :body_html])
67+
68+
Multi.new()
69+
|> Multi.run(:update_article_document, fn _, _ ->
70+
case Map.has_key?(attrs, :title) do
71+
true -> article_doc |> ORM.update(Map.merge(attrs, %{title: attrs.title}))
72+
false -> article_doc |> ORM.update(attrs)
73+
end
74+
end)
75+
|> Multi.run(:update_thread_document, fn _, _ ->
76+
thread_doc |> ORM.update(attrs)
77+
end)
78+
|> Repo.transaction()
79+
|> result()
80+
end
81+
end
82+
83+
# 只更新 title 的情况
84+
def update(article, %{title: title} = attrs) do
85+
with {:ok, article_thread} <- thread_of_article(article, :upcase),
86+
{:ok, article_doc} <- find_article_document(article_thread, article) do
87+
article_doc |> ORM.update(%{title: attrs.title})
88+
end
89+
end
90+
91+
def update(article, _), do: {:ok, article}
92+
93+
defp find_article_document(article_thread, article) do
94+
ORM.find_by(ArticleDocument, %{article_id: article.id, thread: article_thread})
95+
end
96+
97+
defp find_thread_document(article_thread, article) do
98+
CMS.Model
99+
|> Module.concat("#{Recase.to_title(article_thread)}Document")
100+
|> ORM.find_by(Map.put(%{}, foreign_key(article_thread), article.id))
101+
end
102+
103+
@doc """
104+
remove article document foever
105+
"""
106+
def remove(thread, id) do
107+
thread = thread |> to_string |> String.upcase()
108+
109+
ArticleDocument |> ORM.findby_delete!(%{thread: thread, article_id: id})
110+
end
111+
112+
defp foreign_key(article_thread) do
113+
thread_atom = article_thread |> String.downcase() |> String.to_atom()
114+
115+
:"#{thread_atom}_id"
116+
end
117+
118+
defp result({:ok, %{create_thread_document: result}}), do: {:ok, result}
119+
defp result({:ok, %{update_article_document: result}}), do: {:ok, result}
120+
121+
defp result({:error, _, _result, _steps}) do
122+
{:error, [message: "create document", code: ecode(:create_fails)]}
123+
end
124+
end

lib/groupher_server/cms/delegates/hooks/cite.ex

+6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ defmodule GroupherServer.CMS.Delegate.Hooks.Cite do
6565
end
6666
end
6767

68+
def handle(%{document: document} = article) do
69+
body = Repo.preload(article, :document) |> get_in([:document, :body])
70+
article = article |> Map.put(:body, body)
71+
handle(article)
72+
end
73+
6874
@doc """
6975
return fmt like:
7076
[

lib/groupher_server/cms/delegates/hooks/mention.ex

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ defmodule GroupherServer.CMS.Delegate.Hooks.Mention do
2727
end
2828
end
2929

30+
def handle(%{document: document} = article) do
31+
body = Repo.preload(article, :document) |> get_in([:document, :body])
32+
article = article |> Map.put(:body, body)
33+
handle(article)
34+
end
35+
3036
defp handle_mentions(mentions, artiment) do
3137
with {:ok, author} <- author_of(artiment) do
3238
Delivery.send(:mention, artiment, mentions, author)

0 commit comments

Comments
 (0)