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

Commit 690bf55

Browse files
authored
feat(articles): add active_at timestamp & sink concept (#377)
* refactor(articles): add active_at timestamp * refactor(articles): fix timestamp fmt * refactor(articles): test active_at field in gq workflow * refactor(articles): update active_at after create comment * refactor(articles): active_period_days config & logic * feat(active_at): use active_at as default sort * feat(active_at): add sort args in PageSizeProof * feat(active_at): add sort args in PageSizeProof * feat(active_at): fix active_at filter tests * fix(active_at): some test error * fix(active_at): some test error * fix(active_at): some test error * fix(active_at): adjust should_add_pin logic by use active_at * fix(active_at): filter time shift test error * fix(active_at): clean up * fix(active_at): paged filter test * fix(active_at): basic sink/undo_sink * fix(active_at): wip * fix(active_at): fix test * fix(active_at): last_active_at concept * fix(active_at): adjust lock_article_comment arg & fmt * fix(active_at): sink gq workflow
1 parent 9725bcb commit 690bf55

Some content is hidden

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

48 files changed

+1145
-174
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ test.watch:
167167
mix test.watch
168168
test.watch.wip:
169169
# work around, see: https://elixirforum.com/t/mix-test-file-watch/12298/2
170-
mix test --listen-on-stdin --stale --trace --only wip
170+
# mix test --listen-on-stdin --stale --trace --only wip
171+
mix test --listen-on-stdin --stale --only wip
171172
# test.watch not work now, see: https://github.com/lpil/mix-test.watch/issues/116
172173
# mix test.watch --only wip --stale
173174
test.watch.wip2:

config/config.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ config :groupher_server, :customization,
6363
config :groupher_server, :article,
6464
# NOTE: do not change unless you know what you are doing
6565
article_threads: [:post, :job, :repo],
66+
# in this period, paged articles will sort front if non-article-author commented
67+
# 在此时间段内,一旦有非文章作者的用户评论,该文章就会排到前面
68+
active_period_days: %{
69+
post: 10,
70+
job: 10,
71+
repo: 10
72+
},
73+
6674
# NOTE: if you want to add/remove emotion, just edit the list below
6775
# and migrate the field to table "articles_users_emotions"
6876
supported_emotions: [

lib/groupher_server/cms/cms.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ defmodule GroupherServer.CMS do
9393
defdelegate mark_delete_article(thread, id), to: ArticleCURD
9494
defdelegate undo_mark_delete_article(thread, id), to: ArticleCURD
9595

96+
defdelegate update_active_timestamp(thread, article), to: ArticleCURD
97+
defdelegate sink_article(thread, id), to: ArticleCURD
98+
defdelegate undo_sink_article(thread, id), to: ArticleCURD
99+
96100
defdelegate upvote_article(thread, article_id, user), to: ArticleUpvote
97101
defdelegate undo_upvote_article(thread, article_id, user), to: ArticleUpvote
98102

@@ -112,7 +116,7 @@ defmodule GroupherServer.CMS do
112116
# >> set flag on article, like: pin / unpin article
113117
defdelegate pin_article(thread, id, community_id), to: ArticleCommunity
114118
defdelegate undo_pin_article(thread, id, community_id), to: ArticleCommunity
115-
defdelegate lock_article_comment(article), to: ArticleCommunity
119+
defdelegate lock_article_comment(thread, article_id), to: ArticleCommunity
116120

117121
# >> community: set / unset
118122
defdelegate mirror_article(thread, article_id, community_id), to: ArticleCommunity

lib/groupher_server/cms/delegates/article_comment.ex

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
105105
|> Multi.run(:add_participator, fn _, _ ->
106106
add_participator_to_article(article, user)
107107
end)
108+
|> Multi.run(:update_article_active_timestamp, fn _, %{create_article_comment: comment} ->
109+
case comment.author_id == article.author.user.id do
110+
true -> {:ok, :pass}
111+
false -> CMS.update_active_timestamp(thread, article)
112+
end
113+
end)
108114
|> Repo.transaction()
109115
|> result()
110116
end
@@ -284,9 +290,5 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
284290
raise_error(:create_comment, result)
285291
end
286292

287-
defp result({:error, :add_participator, result, _steps}) do
288-
{:error, result}
289-
end
290-
291293
defp result({:error, _, result, _steps}), do: {:error, result}
292294
end

lib/groupher_server/cms/delegates/article_community.ex

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommunity do
66
import Ecto.Query, warn: false
77

88
import Helper.ErrorCode
9-
import Helper.Utils, only: [strip_struct: 1, done: 1]
9+
import Helper.Utils, only: [strip_struct: 1, done: 1, ensure: 2]
1010
import GroupherServer.CMS.Helper.Matcher
1111

1212
alias Helper.Types, as: T
@@ -17,6 +17,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommunity do
1717

1818
alias Ecto.Multi
1919

20+
@default_article_meta Embeds.ArticleMeta.default_meta()
2021
@max_pinned_article_count_per_thread Community.max_pinned_article_count_per_thread()
2122

2223
@spec pin_article(T.article_thread(), Integer.t(), Integer.t()) :: {:ok, PinnedArticle.t()}
@@ -144,21 +145,16 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommunity do
144145
def update_edit_status(content, _), do: {:ok, content}
145146

146147
@doc "lock comment of a article"
147-
# TODO: record it to ArticleLog
148-
def lock_article_comment(
149-
%{meta: %Embeds.ArticleMeta{is_comment_locked: false} = meta} = content
150-
) do
151-
meta =
152-
meta
153-
|> Map.from_struct()
154-
|> Map.delete(:id)
155-
|> Map.merge(%{is_comment_locked: true})
148+
def lock_article_comment(thread, id) do
149+
with {:ok, info} <- match(thread),
150+
{:ok, article} <- ORM.find(info.model, id) do
151+
article_meta = ensure(article.meta, @default_article_meta)
152+
meta = Map.merge(article_meta, %{is_comment_locked: true})
156153

157-
ORM.update_meta(content, meta)
154+
ORM.update_meta(article, meta)
155+
end
158156
end
159157

160-
def lock_article_comment(content), do: {:ok, content}
161-
162158
# check if the thread has aready enough pinned articles
163159
defp check_pinned_article_count(community_id, thread) do
164160
thread_upcase = thread |> to_string |> String.upcase()

lib/groupher_server/cms/delegates/article_curd.ex

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
77
import GroupherServer.CMS.Helper.Matcher
88

99
import Helper.Utils,
10-
only: [done: 1, pick_by: 2, integerfy: 1, strip_struct: 1, module_to_thread: 1]
10+
only: [
11+
done: 1,
12+
pick_by: 2,
13+
integerfy: 1,
14+
strip_struct: 1,
15+
module_to_thread: 1,
16+
get_config: 2,
17+
ensure: 2
18+
]
1119

1220
import GroupherServer.CMS.Delegate.Helper, only: [mark_viewer_emotion_states: 2]
1321
import Helper.ErrorCode
@@ -23,6 +31,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
2331

2432
alias Ecto.Multi
2533

34+
@active_period get_config(:article, :active_period_days)
2635
@default_emotions Embeds.ArticleEmotion.default_emotions()
2736
@default_article_meta Embeds.ArticleMeta.default_meta()
2837

@@ -31,7 +40,16 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
3140
"""
3241
def read_article(thread, id) do
3342
with {:ok, info} <- match(thread) do
34-
ORM.read(info.model, id, inc: :views)
43+
Multi.new()
44+
|> Multi.run(:inc_views, fn _, _ -> ORM.read(info.model, id, inc: :views) end)
45+
|> Multi.run(:update_article_meta, fn _, %{inc_views: article} ->
46+
article_meta = ensure(article.meta, @default_article_meta)
47+
meta = Map.merge(article_meta, %{can_undo_sink: in_active_period?(thread, article)})
48+
49+
ORM.update_meta(article, meta)
50+
end)
51+
|> Repo.transaction()
52+
|> result()
3553
end
3654
end
3755

@@ -41,8 +59,12 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
4159
def read_article(thread, id, %User{id: user_id}) do
4260
with {:ok, info} <- match(thread) do
4361
Multi.new()
44-
|> Multi.run(:inc_views, fn _, _ ->
45-
ORM.read(info.model, id, inc: :views)
62+
|> Multi.run(:inc_views, fn _, _ -> ORM.read(info.model, id, inc: :views) end)
63+
|> Multi.run(:update_article_meta, fn _, %{inc_views: article} ->
64+
article_meta = ensure(article.meta, @default_article_meta)
65+
meta = Map.merge(article_meta, %{can_undo_sink: in_active_period?(thread, article)})
66+
67+
ORM.update_meta(article, meta)
4668
end)
4769
|> Multi.run(:add_viewed_user, fn _, %{inc_views: article} ->
4870
update_viewed_user_list(article, user_id)
@@ -142,6 +164,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
142164
|> Multi.run(:set_article_tags, fn _, %{create_article: article} ->
143165
ArticleTag.set_article_tags(community, thread, article, attrs)
144166
end)
167+
|> Multi.run(:set_active_at_timestamp, fn _, %{create_article: article} ->
168+
ORM.update(article, %{active_at: article.inserted_at})
169+
end)
145170
|> Multi.run(:update_community_article_count, fn _, _ ->
146171
CommunityCURD.update_community_count_field(community, thread)
147172
end)
@@ -156,7 +181,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
156181
Statistics.log_publish_action(%User{id: uid})
157182
end)
158183
|> Repo.transaction()
159-
|> create_article_result()
184+
|> result()
160185
end
161186
end
162187

@@ -199,6 +224,55 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
199224
|> result()
200225
end
201226

227+
@doc """
228+
update active at timestamp of an article
229+
"""
230+
def update_active_timestamp(thread, article) do
231+
# @article_active_period
232+
# 1. 超过时限不更新
233+
# 2. 已经沉默的不更新, is_sinked
234+
with true <- in_active_period?(thread, article) do
235+
ORM.update(article, %{active_at: DateTime.utc_now()})
236+
else
237+
_ -> {:ok, :pass}
238+
end
239+
end
240+
241+
@doc """
242+
sink article
243+
"""
244+
def sink_article(thread, id) do
245+
with {:ok, info} <- match(thread),
246+
{:ok, article} <- ORM.find(info.model, id) do
247+
meta = Map.merge(article.meta, %{is_sinked: true, last_active_at: article.active_at})
248+
ORM.update_meta(article, meta, changes: %{active_at: article.inserted_at})
249+
end
250+
end
251+
252+
@doc """
253+
undo sink article
254+
"""
255+
def undo_sink_article(thread, id) do
256+
with {:ok, info} <- match(thread),
257+
{:ok, article} <- ORM.find(info.model, id),
258+
true <- in_active_period?(thread, article) do
259+
meta = Map.merge(article.meta, %{is_sinked: false})
260+
ORM.update_meta(article, meta, changes: %{active_at: meta.last_active_at})
261+
else
262+
false -> raise_error(:undo_sink_old_article, "can not undo sink old article")
263+
end
264+
end
265+
266+
# check is an article's active_at is in active period
267+
defp in_active_period?(thread, article) do
268+
active_period_days = Map.get(@active_period, thread)
269+
270+
inserted_at = article.inserted_at
271+
active_threshold = Timex.shift(Timex.now(), days: -active_period_days)
272+
273+
:gt == DateTime.compare(inserted_at, active_threshold)
274+
end
275+
202276
@doc """
203277
mark delete falst for an anticle
204278
"""
@@ -300,7 +374,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
300374
defp add_pin_articles_ifneed(articles, _querable, _filter), do: articles
301375

302376
# if filter contains like: tags, sort.., then don't add pin article
303-
defp should_add_pin?(%{page: 1, sort: :desc_inserted} = filter) do
377+
defp should_add_pin?(%{page: 1, sort: :desc_active} = filter) do
304378
skip_pinned_fields = [:article_tag, :article_tags]
305379

306380
not Enum.any?(Map.keys(filter), &(&1 in skip_pinned_fields))
@@ -331,35 +405,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
331405
|> Map.put(:total_count, normal_count)
332406
end
333407

334-
defp create_article_result({:ok, %{create_article: result}}) do
335-
Later.exec({__MODULE__, :notify_admin_new_article, [result]})
336-
{:ok, result}
337-
end
338-
339-
defp create_article_result({:error, :create_article, %Ecto.Changeset{} = result, _steps}) do
340-
{:error, result}
341-
end
342-
343-
defp create_article_result({:error, :create_article, _result, _steps}) do
344-
{:error, [message: "create cms article author", code: ecode(:create_fails)]}
345-
end
346-
347-
defp create_article_result({:error, :mirror_article, _result, _steps}) do
348-
{:error, [message: "set community", code: ecode(:create_fails)]}
349-
end
350-
351-
defp create_article_result({:error, :set_community_flag, _result, _steps}) do
352-
{:error, [message: "set community flag", code: ecode(:create_fails)]}
353-
end
354-
355-
defp create_article_result({:error, :set_article_tags, result, _steps}) do
356-
{:error, result}
357-
end
358-
359-
defp create_article_result({:error, :log_action, _result, _steps}) do
360-
{:error, [message: "log action", code: ecode(:create_fails)]}
361-
end
362-
363408
# for create artilce step in Multi.new
364409
defp do_create_article(target, attrs, %Author{id: aid}, %Community{id: cid}) do
365410
target
@@ -396,9 +441,32 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
396441
end
397442
end
398443

444+
# create done
445+
defp result({:ok, %{set_active_at_timestamp: result}}) do
446+
Later.exec({__MODULE__, :notify_admin_new_article, [result]})
447+
{:ok, result}
448+
end
449+
399450
defp result({:ok, %{update_edit_status: result}}), do: {:ok, result}
400451
defp result({:ok, %{update_article: result}}), do: {:ok, result}
401452
defp result({:ok, %{set_viewer_has_states: result}}), do: result |> done()
453+
defp result({:ok, %{update_article_meta: result}}), do: {:ok, result}
454+
455+
defp result({:error, :create_article, _result, _steps}) do
456+
{:error, [message: "create cms article author", code: ecode(:create_fails)]}
457+
end
458+
459+
defp result({:error, :mirror_article, _result, _steps}) do
460+
{:error, [message: "set community", code: ecode(:create_fails)]}
461+
end
462+
463+
defp result({:error, :set_community_flag, _result, _steps}) do
464+
{:error, [message: "set community flag", code: ecode(:create_fails)]}
465+
end
466+
467+
defp result({:error, :log_action, _result, _steps}) do
468+
{:error, [message: "log action", code: ecode(:create_fails)]}
469+
end
402470

403471
defp result({:error, _, result, _steps}), do: {:error, result}
404472
end

lib/groupher_server/cms/embeds/article_meta.ex

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,23 @@ defmodule GroupherServer.CMS.Embeds.ArticleMeta do
66
use Accessible
77
import Ecto.Changeset
88

9-
@optional_fields ~w(is_edited is_comment_locked upvoted_user_ids collected_user_ids viewed_user_ids reported_user_ids reported_count)a
10-
11-
@default_meta %{
12-
is_edited: false,
13-
is_comment_locked: false,
14-
upvoted_user_ids: [],
15-
collected_user_ids: [],
16-
viewed_user_ids: [],
17-
reported_user_ids: [],
18-
reported_count: 0
19-
}
9+
@optional_fields ~w(is_edited is_comment_locked upvoted_user_ids collected_user_ids viewed_user_ids reported_user_ids reported_count is_sinked can_undo_sink last_active_at)a
2010

2111
@doc "for test usage"
22-
def default_meta(), do: @default_meta
12+
def default_meta() do
13+
%{
14+
is_edited: false,
15+
is_comment_locked: false,
16+
upvoted_user_ids: [],
17+
collected_user_ids: [],
18+
viewed_user_ids: [],
19+
reported_user_ids: [],
20+
reported_count: 0,
21+
is_sinked: false,
22+
can_undo_sink: true,
23+
last_active_at: nil
24+
}
25+
end
2326

2427
embedded_schema do
2528
field(:is_edited, :boolean, default: false)
@@ -30,6 +33,11 @@ defmodule GroupherServer.CMS.Embeds.ArticleMeta do
3033
field(:viewed_user_ids, {:array, :integer}, default: [])
3134
field(:reported_user_ids, {:array, :integer}, default: [])
3235
field(:reported_count, :integer, default: 0)
36+
37+
field(:is_sinked, :boolean, default: false)
38+
field(:can_undo_sink, :boolean, default: false)
39+
# if undo_sink, can recover last active_at from here
40+
field(:last_active_at, :utc_datetime_usec)
3341
end
3442

3543
def changeset(struct, params) do

lib/groupher_server/cms/helper/macros.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ defmodule GroupherServer.CMS.Helper.Macros do
121121
:article_comments_participators_count,
122122
:upvotes_count,
123123
:collects_count,
124-
:mark_delete
124+
:mark_delete,
125+
:active_at
125126
]
126127
end
127128

@@ -189,6 +190,7 @@ defmodule GroupherServer.CMS.Helper.Macros do
189190
viewer_has_fields()
190191
article_comment_fields()
191192

193+
field(:active_at, :utc_datetime_usec)
192194
# TODO:
193195
# reference_articles
194196
# related_articles

0 commit comments

Comments
 (0)