Working metrics for all forms of interactions and updated .env.example

This commit is contained in:
2026-04-07 00:09:10 -04:00
parent 76a8acc731
commit 3c9910a723
4 changed files with 89 additions and 24 deletions

View File

@@ -33,7 +33,8 @@ defmodule Mixer.Metrics do
# Event types
# ---------------------------------------------------------------------------
@type event_type :: :view | :like | :unlike | :comment | :share
@type event_type ::
:view | :post | :comment | :like | :unlike | :share | :delete_post | :delete_comment
@type track_opt ::
{:user_id, binary() | nil}
@@ -70,6 +71,33 @@ defmodule Mixer.Metrics do
@spec track_share(binary(), [track_opt()]) :: :ok
def track_share(tweet_id, opts \\ []), do: enqueue("share", tweet_id, opts)
@doc """
Track a new top-level tweet being published.
The event is recorded against the new tweet's own ID.
"""
@spec track_post(binary(), [track_opt()]) :: :ok
def track_post(tweet_id, opts \\ []), do: enqueue("post", tweet_id, opts)
@doc """
Track a top-level tweet being deleted.
The event is recorded against the deleted tweet's ID.
Note: cascade-deleted comments are not individually tracked — only the
explicit user-initiated destroy action emits this event.
"""
@spec track_delete_post(binary(), [track_opt()]) :: :ok
def track_delete_post(tweet_id, opts \\ []), do: enqueue("delete_post", tweet_id, opts)
@doc """
Track a comment (reply) being deleted.
The event is recorded against the *parent* tweet's ID so that
`get_summary/1` can reflect net comment activity on a tweet.
"""
@spec track_delete_comment(binary(), [track_opt()]) :: :ok
def track_delete_comment(tweet_id, opts \\ []), do: enqueue("delete_comment", tweet_id, opts)
# ---------------------------------------------------------------------------
# Query helpers
# ---------------------------------------------------------------------------

View File

@@ -8,13 +8,16 @@ defmodule Mixer.Metrics.PostEvent do
## Event types
| event_type | Description |
|-------------|------------------------------------------|
| `"view"` | A tweet was displayed to a user |
| `"like"` | A user liked a tweet |
| `"unlike"` | A user removed their like from a tweet |
| `"comment"` | A user replied to a tweet |
| `"share"` | A user shared / reposted a tweet |
| event_type | `tweet_id` refers to | Description |
|--------------------|-----------------------|-------------------------------------------------|
| `"view"` | the viewed tweet | Tweet detail page was loaded |
| `"post"` | the new tweet | A new top-level tweet was published |
| `"comment"` | the parent tweet | A reply was posted; count against the parent |
| `"like"` | the liked tweet | A user liked a tweet |
| `"unlike"` | the unliked tweet | A user removed their like |
| `"share"` | the shared tweet | A user shared / reposted a tweet |
| `"delete_post"` | the deleted tweet | A top-level tweet was deleted by its author |
| `"delete_comment"` | the parent tweet | A reply was deleted; count against the parent |
"""
use Ecto.Schema

View File

@@ -32,7 +32,7 @@ defmodule Mixer.Posts.Tweet do
end
actions do
defaults [:read, :destroy]
defaults [:read]
read :following_feed do
filter expr(
@@ -67,24 +67,47 @@ defmodule Mixer.Posts.Tweet do
end
end
# Track a "comment" metric event whenever a reply is posted. We record
# the event against the *parent* tweet so that `get_summary/1` and
# `get_bulk_summaries/1` can count how many comments each tweet received.
# Track post / comment creation metrics.
# Root tweets emit a "post" event recorded against their own ID.
# Replies emit a "comment" event recorded against the parent tweet ID so
# that `get_summary/1` can count how many replies a tweet has received.
change fn changeset, context ->
case Ash.Changeset.get_attribute(changeset, :parent_tweet_id) do
nil ->
changeset
parent_tweet_id = Ash.Changeset.get_attribute(changeset, :parent_tweet_id)
user_id = context.actor && context.actor.id
parent_tweet_id ->
Ash.Changeset.after_action(changeset, fn _changeset, tweet ->
Mixer.Metrics.track_comment(
parent_tweet_id,
user_id: context.actor && context.actor.id
)
Ash.Changeset.after_action(changeset, fn _changeset, tweet ->
if parent_tweet_id do
Mixer.Metrics.track_comment(parent_tweet_id, user_id: user_id)
else
Mixer.Metrics.track_post(tweet.id, user_id: user_id)
end
{:ok, tweet}
end)
end
{:ok, tweet}
end)
end
end
# Explicit destroy so we can attach a metrics hook. The policy and cascade
# behaviour are identical to the previous default :destroy action.
destroy :destroy do
require_atomic? false
change fn changeset, context ->
# Capture the record's identity *before* deletion — after the action
# completes the row no longer exists.
tweet_id = changeset.data.id
parent_tweet_id = changeset.data.parent_tweet_id
user_id = context.actor && context.actor.id
Ash.Changeset.after_action(changeset, fn _changeset, result ->
if parent_tweet_id do
Mixer.Metrics.track_delete_comment(parent_tweet_id, user_id: user_id)
else
Mixer.Metrics.track_delete_post(tweet_id, user_id: user_id)
end
{:ok, result}
end)
end
end