Adding likes to tweets
This commit is contained in:
@@ -304,6 +304,8 @@ defmodule Mixer.Accounts.User do
|
||||
filter expr(valid)
|
||||
end
|
||||
|
||||
has_many :tweet_likes, Mixer.Posts.TweetLike
|
||||
|
||||
has_many :tweets, Mixer.Posts.Tweet
|
||||
end
|
||||
|
||||
|
||||
@@ -9,13 +9,16 @@ defmodule Mixer.Posts do
|
||||
|
||||
resources do
|
||||
resource Mixer.Posts.Tweet
|
||||
resource Mixer.Posts.TweetLike
|
||||
resource Mixer.Posts.Media
|
||||
end
|
||||
|
||||
typescript_rpc do
|
||||
resource Mixer.Posts.Tweet do
|
||||
rpc_action :create_tweet, :create
|
||||
rpc_action :like_tweet, :like
|
||||
rpc_action :read_tweet, :read
|
||||
rpc_action :unlike_tweet, :unlike
|
||||
rpc_action :update_tweet, :update
|
||||
rpc_action :destroy_tweet, :destroy
|
||||
end
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule Mixer.Posts.Tweet do
|
||||
import Ash.Expr
|
||||
require Ash.Query
|
||||
|
||||
use Ash.Resource,
|
||||
otp_app: :mixer,
|
||||
domain: Mixer.Posts,
|
||||
@@ -25,7 +28,7 @@ defmodule Mixer.Posts.Tweet do
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:read, :destroy, update: :*]
|
||||
defaults [:read, :destroy]
|
||||
|
||||
create :create do
|
||||
upsert? true
|
||||
@@ -50,6 +53,62 @@ defmodule Mixer.Posts.Tweet do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
update :update do
|
||||
accept [:content]
|
||||
end
|
||||
|
||||
update :like do
|
||||
accept []
|
||||
require_atomic? false
|
||||
|
||||
change fn changeset, context ->
|
||||
Ash.Changeset.after_action(changeset, fn _changeset, tweet ->
|
||||
case ensure_like(tweet, context.actor) do
|
||||
{:created, _like} ->
|
||||
increment_likes(tweet, context.actor)
|
||||
|
||||
{:noop, _like} ->
|
||||
{:ok, tweet}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
update :unlike do
|
||||
accept []
|
||||
require_atomic? false
|
||||
|
||||
change fn changeset, context ->
|
||||
Ash.Changeset.after_action(changeset, fn _changeset, tweet ->
|
||||
case remove_like(tweet, context.actor) do
|
||||
{:deleted, _like} ->
|
||||
decrement_likes(tweet, context.actor)
|
||||
|
||||
{:noop, _like} ->
|
||||
{:ok, tweet}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
update :increment_likes do
|
||||
accept []
|
||||
require_atomic? false
|
||||
change atomic_update(:likes, expr(likes + 1))
|
||||
end
|
||||
|
||||
update :decrement_likes do
|
||||
accept []
|
||||
require_atomic? false
|
||||
change atomic_update(:likes, expr(likes - 1))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -83,6 +142,15 @@ defmodule Mixer.Posts.Tweet do
|
||||
has_many :media, Mixer.Posts.Media do
|
||||
public? true
|
||||
end
|
||||
|
||||
has_many :tweet_likes, Mixer.Posts.TweetLike
|
||||
end
|
||||
|
||||
aggregates do
|
||||
exists :liked_by_me, :tweet_likes do
|
||||
public? true
|
||||
filter expr(user_id == ^actor(:id))
|
||||
end
|
||||
end
|
||||
|
||||
policies do
|
||||
@@ -94,8 +162,93 @@ defmodule Mixer.Posts.Tweet do
|
||||
authorize_if actor_present()
|
||||
end
|
||||
|
||||
policy action_type([:destroy, :update]) do
|
||||
policy action(:update) do
|
||||
authorize_if relates_to_actor_via(:user)
|
||||
end
|
||||
|
||||
policy action(:destroy) do
|
||||
authorize_if relates_to_actor_via(:user)
|
||||
end
|
||||
|
||||
policy action(:like) do
|
||||
authorize_if actor_present()
|
||||
end
|
||||
|
||||
policy action(:unlike) do
|
||||
authorize_if actor_present()
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_like(_tweet, nil), do: {:error, Ash.Error.Forbidden.exception([])}
|
||||
|
||||
defp ensure_like(tweet, actor) do
|
||||
case get_like(tweet.id, actor.id) do
|
||||
{:ok, nil} ->
|
||||
case create_like(tweet.id, actor) do
|
||||
{:ok, like} ->
|
||||
{:created, like}
|
||||
|
||||
{:error, error} ->
|
||||
case get_like(tweet.id, actor.id) do
|
||||
{:ok, nil} ->
|
||||
{:error, error}
|
||||
|
||||
{:ok, like} ->
|
||||
{:noop, like}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, like} ->
|
||||
{:noop, like}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_like(_tweet, nil), do: {:error, Ash.Error.Forbidden.exception([])}
|
||||
|
||||
defp remove_like(tweet, actor) do
|
||||
case get_like(tweet.id, actor.id) do
|
||||
{:ok, nil} ->
|
||||
{:noop, nil}
|
||||
|
||||
{:ok, like} ->
|
||||
case Ash.destroy(like, actor: actor) do
|
||||
:ok -> {:deleted, like}
|
||||
{:ok, _destroyed_like} -> {:deleted, like}
|
||||
{:error, error} -> {:error, error}
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp create_like(tweet_id, actor) do
|
||||
Mixer.Posts.TweetLike
|
||||
|> Ash.Changeset.for_create(:create, %{tweet_id: tweet_id}, actor: actor)
|
||||
|> Ash.create()
|
||||
end
|
||||
|
||||
defp get_like(tweet_id, user_id) do
|
||||
Mixer.Posts.TweetLike
|
||||
|> Ash.Query.filter(expr(tweet_id == ^tweet_id and user_id == ^user_id))
|
||||
|> Ash.read_one(authorize?: false)
|
||||
end
|
||||
|
||||
defp increment_likes(tweet, actor) do
|
||||
tweet
|
||||
|> Ash.Changeset.for_update(:increment_likes, %{}, actor: actor)
|
||||
|> Ash.update(authorize?: false)
|
||||
end
|
||||
|
||||
defp decrement_likes(tweet, actor) do
|
||||
tweet
|
||||
|> Ash.Changeset.for_update(:decrement_likes, %{}, actor: actor)
|
||||
|> Ash.update(authorize?: false)
|
||||
end
|
||||
end
|
||||
|
||||
65
lib/mixer/posts/tweet_like.ex
Normal file
65
lib/mixer/posts/tweet_like.ex
Normal file
@@ -0,0 +1,65 @@
|
||||
defmodule Mixer.Posts.TweetLike do
|
||||
use Ash.Resource,
|
||||
otp_app: :mixer,
|
||||
domain: Mixer.Posts,
|
||||
data_layer: AshPostgres.DataLayer,
|
||||
authorizers: [Ash.Policy.Authorizer]
|
||||
|
||||
postgres do
|
||||
table "tweet_likes"
|
||||
repo Mixer.Repo
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:read, :destroy]
|
||||
|
||||
create :create do
|
||||
accept [:tweet_id]
|
||||
change relate_actor(:user)
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
attribute :tweet_id, :uuid do
|
||||
allow_nil? false
|
||||
end
|
||||
|
||||
attribute :user_id, :uuid do
|
||||
allow_nil? false
|
||||
end
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :tweet, Mixer.Posts.Tweet do
|
||||
attribute_type :uuid
|
||||
attribute_writable? true
|
||||
allow_nil? false
|
||||
end
|
||||
|
||||
belongs_to :user, Mixer.Accounts.User do
|
||||
attribute_type :uuid
|
||||
attribute_writable? true
|
||||
allow_nil? false
|
||||
end
|
||||
end
|
||||
|
||||
identities do
|
||||
identity :unique_user_tweet, [:tweet_id, :user_id]
|
||||
end
|
||||
|
||||
policies do
|
||||
policy action_type(:read) do
|
||||
authorize_if always()
|
||||
end
|
||||
|
||||
policy action(:create) do
|
||||
authorize_if actor_present()
|
||||
end
|
||||
|
||||
policy action_type(:destroy) do
|
||||
authorize_if relates_to_actor_via(:user)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user