|
| 1 | +defmodule GroupherServer.CMS.Delegate.CommentCURD do |
| 2 | + @moduledoc """ |
| 3 | + CURD for comments |
| 4 | + """ |
| 5 | + import Ecto.Query, warn: false |
| 6 | + import Helper.Utils, only: [done: 1] |
| 7 | + import Helper.ErrorCode |
| 8 | + |
| 9 | + import ShortMaps |
| 10 | + |
| 11 | + alias GroupherServer.{Accounts, CMS, Delivery, Repo} |
| 12 | + |
| 13 | + alias Accounts.Model.User |
| 14 | + alias CMS.Model.{Post, PostComment, PostCommentReply} |
| 15 | + |
| 16 | + alias Helper.{ORM, QueryBuilder} |
| 17 | + alias Ecto.Multi |
| 18 | + |
| 19 | + defp match_action(:post, :comment), |
| 20 | + do: {:ok, %{target: Post, reactor: PostComment, preload: :author}} |
| 21 | + |
| 22 | + @doc """ |
| 23 | + Creates a comment for psot, job ... |
| 24 | + """ |
| 25 | + def create_comment( |
| 26 | + thread, |
| 27 | + content_id, |
| 28 | + %{community: community, body: body} = args, |
| 29 | + %User{id: user_id} |
| 30 | + ) do |
| 31 | + with {:ok, action} <- match_action(:post, :comment), |
| 32 | + {:ok, content} <- ORM.find(action.target, content_id), |
| 33 | + {:ok, user} <- ORM.find(User, user_id) do |
| 34 | + Multi.new() |
| 35 | + |> Multi.run(:create_comment, fn _, _ -> |
| 36 | + do_create_comment(thread, action, content, body, user) |
| 37 | + end) |
| 38 | + |> Multi.run(:mention_users, fn _, %{create_comment: comment} -> |
| 39 | + Delivery.mention_from_comment(community, thread, content, comment, args, user) |
| 40 | + {:ok, :pass} |
| 41 | + end) |
| 42 | + |> Repo.transaction() |
| 43 | + |> create_comment_result() |
| 44 | + end |
| 45 | + end |
| 46 | + |
| 47 | + def reply_comment( |
| 48 | + thread, |
| 49 | + comment_id, |
| 50 | + %{community: community, body: body} = args, |
| 51 | + %User{id: user_id} = user |
| 52 | + ) do |
| 53 | + with {:ok, action} <- match_action(:post, :comment), |
| 54 | + {:ok, comment} <- ORM.find(action.reactor, comment_id) do |
| 55 | + next_floor = get_next_floor(thread, action.reactor, comment) |
| 56 | + |
| 57 | + attrs = %{ |
| 58 | + author_id: user_id, |
| 59 | + body: body, |
| 60 | + reply_to: comment, |
| 61 | + floor: next_floor, |
| 62 | + mention_users: Map.get(args, :mention_users, []) |
| 63 | + } |
| 64 | + |
| 65 | + Delivery.mention_from_comment_reply(community, thread, comment, attrs, user) |
| 66 | + attrs = merge_reply_attrs(thread, attrs, comment) |
| 67 | + bridge_reply(thread, action.reactor, comment, attrs) |
| 68 | + end |
| 69 | + end |
| 70 | + |
| 71 | + @doc """ |
| 72 | + Creates a comment for psot, job ... |
| 73 | + """ |
| 74 | + def update_comment(thread, id, %{body: body}, %User{id: user_id}) do |
| 75 | + with {:ok, action} <- match_action(:post, :comment), |
| 76 | + {:ok, content} <- ORM.find(action.reactor, id), |
| 77 | + true <- content.author_id == user_id do |
| 78 | + ORM.update(content, %{body: body}) |
| 79 | + end |
| 80 | + end |
| 81 | + |
| 82 | + @doc """ |
| 83 | + Delete the comment and increase all the floor after this comment |
| 84 | + """ |
| 85 | + def delete_comment(thread, content_id) do |
| 86 | + with {:ok, action} <- match_action(:post, :comment), |
| 87 | + {:ok, comment} <- ORM.find(action.reactor, content_id) do |
| 88 | + Multi.new() |
| 89 | + |> Multi.run(:delete_comment, fn _, _ -> |
| 90 | + ORM.delete(comment) |
| 91 | + end) |
| 92 | + |> Multi.run(:update_floor, fn _, _ -> |
| 93 | + exec_update_floor(action.reactor, comment) |
| 94 | + end) |
| 95 | + |> Repo.transaction() |
| 96 | + |> delete_comment_result() |
| 97 | + end |
| 98 | + end |
| 99 | + |
| 100 | + @doc """ |
| 101 | + list paged comments |
| 102 | + """ |
| 103 | + def paged_comments(thread, content_id, %{page: page, size: size} = filters) do |
| 104 | + with {:ok, action} <- match_action(:post, :comment) do |
| 105 | + dynamic = dynamic_comment_where(thread, content_id) |
| 106 | + |
| 107 | + action.reactor |
| 108 | + |> where(^dynamic) |
| 109 | + |> QueryBuilder.filter_pack(filters) |
| 110 | + |> ORM.paginater(~m(page size)a) |
| 111 | + |> done() |
| 112 | + end |
| 113 | + end |
| 114 | + |
| 115 | + @doc """ |
| 116 | + list paged comments participators |
| 117 | + """ |
| 118 | + def paged_comments_participators(thread, content_id, %{page: page, size: size} = filters) do |
| 119 | + with {:ok, action} <- match_action(:post, :comment) do |
| 120 | + dynamic = dynamic_comment_where(thread, content_id) |
| 121 | + |
| 122 | + action.reactor |
| 123 | + |> where(^dynamic) |
| 124 | + |> QueryBuilder.filter_pack(filters) |
| 125 | + |> join(:inner, [c], a in assoc(c, :author)) |
| 126 | + |> distinct([c, a], a.id) |
| 127 | + # new added when upgrade to ecto v3 |
| 128 | + |> group_by([c, a], a.id) |
| 129 | + |> group_by([c, a], c.inserted_at) |
| 130 | + # new added when upgrade to ecto v3 end |
| 131 | + |> select([c, a], a) |
| 132 | + |> ORM.paginater(~m(page size)a) |
| 133 | + |> done() |
| 134 | + end |
| 135 | + end |
| 136 | + |
| 137 | + def paged_replies(thread, comment_id, %User{id: user_id}) do |
| 138 | + with {:ok, action} <- match_action(:post, :comment) do |
| 139 | + action.reactor |
| 140 | + |> where([c], c.author_id == ^user_id) |
| 141 | + |> join(:inner, [c], r in assoc(c, :reply_to)) |
| 142 | + |> where([c, r], r.id == ^comment_id) |
| 143 | + |> Repo.all() |
| 144 | + |> done() |
| 145 | + end |
| 146 | + end |
| 147 | + |
| 148 | + defp do_create_comment(thread, action, content, body, user) do |
| 149 | + next_floor = get_next_floor(thread, action.reactor, content.id) |
| 150 | + |
| 151 | + attrs = %{ |
| 152 | + author_id: user.id, |
| 153 | + body: body, |
| 154 | + floor: next_floor |
| 155 | + } |
| 156 | + |
| 157 | + attrs = merge_comment_attrs(thread, attrs, content.id) |
| 158 | + |
| 159 | + action.reactor |> ORM.create(attrs) |
| 160 | + end |
| 161 | + |
| 162 | + defp create_comment_result({:ok, %{create_comment: result}}), do: {:ok, result} |
| 163 | + |
| 164 | + defp create_comment_result({:error, :create_comment, result, _steps}) do |
| 165 | + {:error, result} |
| 166 | + end |
| 167 | + |
| 168 | + defp delete_comment_result({:ok, %{delete_comment: result}}), do: {:ok, result} |
| 169 | + |
| 170 | + defp delete_comment_result({:error, :delete_comment, _result, _steps}) do |
| 171 | + {:error, [message: "delete comment fails", code: ecode(:delete_fails)]} |
| 172 | + end |
| 173 | + |
| 174 | + defp delete_comment_result({:error, :update_floor, _result, _steps}) do |
| 175 | + {:error, [message: "update follor fails", code: ecode(:delete_fails)]} |
| 176 | + end |
| 177 | + |
| 178 | + defp exec_update_floor(queryable, comment) do |
| 179 | + Repo.update_all( |
| 180 | + from(p in queryable, where: p.id > ^comment.id), |
| 181 | + inc: [floor: -1] |
| 182 | + ) |
| 183 | + |> done() |
| 184 | + |> case do |
| 185 | + {:ok, _} -> {:ok, comment} |
| 186 | + {:error, _} -> {:error, ""} |
| 187 | + end |
| 188 | + end |
| 189 | + |
| 190 | + # simulate a join connection |
| 191 | + # TODO: use Multi task to refactor |
| 192 | + # TODO: refactor boilerplate code |
| 193 | + defp bridge_reply(:post, queryable, comment, attrs) do |
| 194 | + with {:ok, reply} <- ORM.create(queryable, attrs) do |
| 195 | + ORM.update(reply, %{reply_id: comment.id}) |
| 196 | + |
| 197 | + {:ok, _} = |
| 198 | + PostCommentReply |> ORM.create(%{post_comment_id: comment.id, reply_id: reply.id}) |
| 199 | + |
| 200 | + queryable |> ORM.find(reply.id) |
| 201 | + end |
| 202 | + end |
| 203 | + |
| 204 | + # for create comment |
| 205 | + defp get_next_floor(thread, queryable, id) when is_integer(id) do |
| 206 | + dynamic = dynamic_comment_where(thread, id) |
| 207 | + |
| 208 | + queryable |
| 209 | + |> where(^dynamic) |
| 210 | + |> ORM.next_count() |
| 211 | + end |
| 212 | + |
| 213 | + # for reply comment |
| 214 | + defp get_next_floor(thread, queryable, comment) do |
| 215 | + dynamic = dynamic_reply_where(thread, comment) |
| 216 | + |
| 217 | + queryable |
| 218 | + |> where(^dynamic) |
| 219 | + |> ORM.next_count() |
| 220 | + end |
| 221 | + |
| 222 | + # merge_comment_attrs when create comemnt |
| 223 | + defp merge_comment_attrs(:post, attrs, id), do: attrs |> Map.merge(%{post_id: id}) |
| 224 | + defp merge_comment_attrs(:job, attrs, id), do: attrs |> Map.merge(%{job_id: id}) |
| 225 | + defp merge_comment_attrs(:repo, attrs, id), do: attrs |> Map.merge(%{repo_id: id}) |
| 226 | + |
| 227 | + defp merge_reply_attrs(:post, attrs, comment), |
| 228 | + do: attrs |> Map.merge(%{post_id: comment.post_id}) |
| 229 | + |
| 230 | + defp merge_reply_attrs(:job, attrs, comment), do: attrs |> Map.merge(%{job_id: comment.job_id}) |
| 231 | + |
| 232 | + defp merge_reply_attrs(:repo, attrs, comment), |
| 233 | + do: attrs |> Map.merge(%{repo_id: comment.repo_id}) |
| 234 | + |
| 235 | + defp dynamic_comment_where(:post, id), do: dynamic([c], c.post_id == ^id) |
| 236 | + defp dynamic_comment_where(:job, id), do: dynamic([c], c.job_id == ^id) |
| 237 | + defp dynamic_comment_where(:repo, id), do: dynamic([c], c.repo_id == ^id) |
| 238 | + |
| 239 | + defp dynamic_reply_where(:post, comment), do: dynamic([c], c.post_id == ^comment.post_id) |
| 240 | + defp dynamic_reply_where(:job, comment), do: dynamic([c], c.job_id == ^comment.job_id) |
| 241 | + defp dynamic_reply_where(:repo, comment), do: dynamic([c], c.repo_id == ^comment.repo_id) |
| 242 | +end |
0 commit comments