some reformatting and adjusting so logged in users get moved directly to their feed

This commit is contained in:
2026-04-03 19:40:17 -04:00
parent a926733f1b
commit 874fec835d
19 changed files with 233 additions and 158 deletions

View File

@@ -1,6 +1,18 @@
defmodule Mixer.Accounts do
use Ash.Domain, otp_app: :mixer, extensions: [AshTypescript.Rpc, AshAdmin.Domain]
typescript_rpc do
resource Mixer.Accounts.User do
rpc_action :read_user, :read
end
resource Mixer.Accounts.Follow do
rpc_action :read_follow, :read
rpc_action :follow_user, :follow
rpc_action :unfollow_user, :unfollow
end
end
admin do
show? true
end
@@ -12,15 +24,4 @@ defmodule Mixer.Accounts do
resource Mixer.Accounts.Follow
end
typescript_rpc do
resource Mixer.Accounts.User do
rpc_action :read_user, :read
end
resource Mixer.Accounts.Follow do
rpc_action :read_follow, :read
rpc_action :follow_user, :follow
rpc_action :unfollow_user, :unfollow
end
end
end

View File

@@ -1,5 +1,6 @@
defmodule Mixer.Accounts.Follow do
require Ash.Query
use Ash.Resource,
domain: Mixer.Accounts,
data_layer: AshPostgres.DataLayer,
@@ -20,25 +21,6 @@ defmodule Mixer.Accounts.Follow do
type_name "follows"
end
attributes do
uuid_primary_key :id
create_timestamp :created_at
end
relationships do
belongs_to :follower, Mixer.Accounts.User do
primary_key? true
allow_nil? false
attribute_writable? true
end
belongs_to :following, Mixer.Accounts.User do
primary_key? true
allow_nil? false
attribute_writable? true
end
end
actions do
defaults [:read, :destroy]
@@ -48,6 +30,7 @@ defmodule Mixer.Accounts.Follow do
upsert_identity :unique_follow
accept [:following_id]
change relate_actor(:follower)
validate fn changeset, _context ->
follower_id = Ash.Changeset.get_attribute(changeset, :follower_id)
following_id = Ash.Changeset.get_attribute(changeset, :following_id)
@@ -82,10 +65,6 @@ defmodule Mixer.Accounts.Follow do
end
end
identities do
identity :unique_follow, [:follower_id, :following_id]
end
policies do
policy action_type(:read) do
authorize_if always()
@@ -99,4 +78,27 @@ defmodule Mixer.Accounts.Follow do
authorize_if actor_present()
end
end
attributes do
uuid_primary_key :id
create_timestamp :created_at
end
relationships do
belongs_to :follower, Mixer.Accounts.User do
primary_key? true
allow_nil? false
attribute_writable? true
end
belongs_to :following, Mixer.Accounts.User do
primary_key? true
allow_nil? false
attribute_writable? true
end
end
identities do
identity :unique_follow, [:follower_id, :following_id]
end
end

View File

@@ -31,14 +31,21 @@ defmodule Mixer.Accounts.User.Senders.SendMagicLinkEmail do
defp body(params) do
# NOTE: You may have to change this to match your magic link acceptance URL.
link = url(~p"/magic_link/#{params[:token]}")
email_template("Your magic link", "Hello, #{params[:email]}!", """
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
Use the button below to sign in to Mixer. This link is valid for a short time and can only be used once.
</p>
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
If you didn't request this, you can safely ignore this email.
</p>
""", link, "Sign In to Mixer")
email_template(
"Your magic link",
"Hello, #{params[:email]}!",
"""
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
Use the button below to sign in to Mixer. This link is valid for a short time and can only be used once.
</p>
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
If you didn't request this, you can safely ignore this email.
</p>
""",
link,
"Sign In to Mixer"
)
end
defp email_template(title, greeting, content, button_url, button_label) do

View File

@@ -22,14 +22,21 @@ defmodule Mixer.Accounts.User.Senders.SendNewUserConfirmationEmail do
defp body(params) do
link = url(~p"/confirm_new_user/#{params[:token]}")
email_template("Confirm your email", "Welcome to Mixer!", """
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
Thanks for signing up. Just one more step — confirm your email address to activate your account.
</p>
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
If you didn't create an account on Mixer, you can safely ignore this email.
</p>
""", link, "Confirm Email Address")
email_template(
"Confirm your email",
"Welcome to Mixer!",
"""
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
Thanks for signing up. Just one more step — confirm your email address to activate your account.
</p>
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
If you didn't create an account on Mixer, you can safely ignore this email.
</p>
""",
link,
"Confirm Email Address"
)
end
defp email_template(title, greeting, content, button_url, button_label) do

View File

@@ -22,14 +22,21 @@ defmodule Mixer.Accounts.User.Senders.SendPasswordResetEmail do
defp body(params) do
link = url(~p"/password-reset/#{params[:token]}")
email_template("Reset your password", "Password reset request", """
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
We received a request to reset the password for your Mixer account. Click the button below to choose a new one.
</p>
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
If you didn't request a password reset, you can safely ignore this email — your password will not change.
</p>
""", link, "Reset My Password")
email_template(
"Reset your password",
"Password reset request",
"""
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
We received a request to reset the password for your Mixer account. Click the button below to choose a new one.
</p>
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
If you didn't request a password reset, you can safely ignore this email — your password will not change.
</p>
""",
link,
"Reset My Password"
)
end
defp email_template(title, greeting, content, button_url, button_label) do

View File

@@ -3,16 +3,6 @@ defmodule Mixer.Posts do
otp_app: :mixer,
extensions: [AshTypescript.Rpc, AshAdmin.Domain]
admin do
show? true
end
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
@@ -27,4 +17,14 @@ defmodule Mixer.Posts do
rpc_action :read_media, :read
end
end
admin do
show? true
end
resources do
resource Mixer.Posts.Tweet
resource Mixer.Posts.TweetLike
resource Mixer.Posts.Media
end
end

View File

@@ -38,6 +38,24 @@ defmodule Mixer.Posts.Media do
end
end
policies do
policy action_type(:read) do
authorize_if always()
end
policy action(:upload) do
authorize_if actor_present()
end
policy action(:link_to_tweet) do
authorize_if relates_to_actor_via(:user)
end
policy action_type(:destroy) do
authorize_if relates_to_actor_via(:user)
end
end
attributes do
uuid_primary_key :id
@@ -64,22 +82,4 @@ defmodule Mixer.Posts.Media do
public? true
end
end
policies do
policy action_type(:read) do
authorize_if always()
end
policy action(:upload) do
authorize_if actor_present()
end
policy action(:link_to_tweet) do
authorize_if relates_to_actor_via(:user)
end
policy action_type(:destroy) do
authorize_if relates_to_actor_via(:user)
end
end
end

View File

@@ -10,7 +10,8 @@ defmodule Mixer.Posts.MediaUploader do
if ext in @extensions, do: :ok, else: {:error, "unsupported file type #{ext}"}
end
def storage_dir(_version, {_file, scope}), do: "uploads/media/#{scope.user_id}/#{scope.media_id}"
def storage_dir(_version, {_file, scope}),
do: "uploads/media/#{scope.user_id}/#{scope.media_id}"
def filename(_version, {file, _scope}) do
Path.basename(file.file_name, Path.extname(file.file_name))

View File

@@ -14,10 +14,6 @@ defmodule Mixer.Posts.Tweet do
repo Mixer.Repo
end
typescript do
type_name "tweets"
end
state_machine do
initial_states [:drafted, :posted]
default_initial_state :drafted
@@ -27,6 +23,10 @@ defmodule Mixer.Posts.Tweet do
end
end
typescript do
type_name "tweets"
end
actions do
defaults [:read, :destroy]
@@ -36,6 +36,7 @@ defmodule Mixer.Posts.Tweet do
argument :media_id, :uuid, allow_nil?: true
change relate_actor(:user)
change transition_state(:posted)
change fn changeset, context ->
case Ash.Changeset.get_argument(changeset, :media_id) do
nil ->
@@ -45,7 +46,9 @@ defmodule Mixer.Posts.Tweet do
Ash.Changeset.after_action(changeset, fn _changeset, tweet ->
Mixer.Posts.Media
|> Ash.get!(media_id, authorize?: false)
|> Ash.Changeset.for_update(:link_to_tweet, %{tweet_id: tweet.id}, actor: context.actor)
|> Ash.Changeset.for_update(:link_to_tweet, %{tweet_id: tweet.id},
actor: context.actor
)
|> Ash.update!()
{:ok, tweet}
@@ -111,6 +114,32 @@ defmodule Mixer.Posts.Tweet do
end
end
policies do
policy action_type(:read) do
authorize_if always()
end
policy action_type(:create) do
authorize_if actor_present()
end
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
attributes do
uuid_primary_key :id
@@ -165,32 +194,6 @@ defmodule Mixer.Posts.Tweet do
end
end
policies do
policy action_type(:read) do
authorize_if always()
end
policy action_type(:create) do
authorize_if actor_present()
end
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

View File

@@ -23,6 +23,20 @@ defmodule Mixer.Posts.TweetLike do
end
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
attributes do
uuid_primary_key :id
@@ -52,18 +66,4 @@ defmodule Mixer.Posts.TweetLike do
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