some reformatting and adjusting so logged in users get moved directly to their feed
This commit is contained in:
@@ -126,7 +126,17 @@ config :esbuild,
|
|||||||
args:
|
args:
|
||||||
~w(js/index.tsx js/app.js --bundle --target=es2022 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --alias:@=. --splitting --format=esm),
|
~w(js/index.tsx js/app.js --bundle --target=es2022 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --alias:@=. --splitting --format=esm),
|
||||||
cd: Path.expand("../assets", __DIR__),
|
cd: Path.expand("../assets", __DIR__),
|
||||||
env: %{"NODE_PATH" => Enum.join([Path.expand("../deps", __DIR__), Path.expand(Mix.Project.build_path()), Path.expand("../_build/dev", __DIR__)], ":")}
|
env: %{
|
||||||
|
"NODE_PATH" =>
|
||||||
|
Enum.join(
|
||||||
|
[
|
||||||
|
Path.expand("../deps", __DIR__),
|
||||||
|
Path.expand(Mix.Project.build_path()),
|
||||||
|
Path.expand("../_build/dev", __DIR__)
|
||||||
|
],
|
||||||
|
":"
|
||||||
|
)
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
# Configure tailwind (the version is required)
|
# Configure tailwind (the version is required)
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
defmodule Mixer.Accounts do
|
defmodule Mixer.Accounts do
|
||||||
use Ash.Domain, otp_app: :mixer, extensions: [AshTypescript.Rpc, AshAdmin.Domain]
|
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
|
admin do
|
||||||
show? true
|
show? true
|
||||||
end
|
end
|
||||||
@@ -12,15 +24,4 @@ defmodule Mixer.Accounts do
|
|||||||
|
|
||||||
resource Mixer.Accounts.Follow
|
resource Mixer.Accounts.Follow
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
defmodule Mixer.Accounts.Follow do
|
defmodule Mixer.Accounts.Follow do
|
||||||
require Ash.Query
|
require Ash.Query
|
||||||
|
|
||||||
use Ash.Resource,
|
use Ash.Resource,
|
||||||
domain: Mixer.Accounts,
|
domain: Mixer.Accounts,
|
||||||
data_layer: AshPostgres.DataLayer,
|
data_layer: AshPostgres.DataLayer,
|
||||||
@@ -20,25 +21,6 @@ defmodule Mixer.Accounts.Follow do
|
|||||||
type_name "follows"
|
type_name "follows"
|
||||||
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
|
|
||||||
|
|
||||||
actions do
|
actions do
|
||||||
defaults [:read, :destroy]
|
defaults [:read, :destroy]
|
||||||
|
|
||||||
@@ -48,6 +30,7 @@ defmodule Mixer.Accounts.Follow do
|
|||||||
upsert_identity :unique_follow
|
upsert_identity :unique_follow
|
||||||
accept [:following_id]
|
accept [:following_id]
|
||||||
change relate_actor(:follower)
|
change relate_actor(:follower)
|
||||||
|
|
||||||
validate fn changeset, _context ->
|
validate fn changeset, _context ->
|
||||||
follower_id = Ash.Changeset.get_attribute(changeset, :follower_id)
|
follower_id = Ash.Changeset.get_attribute(changeset, :follower_id)
|
||||||
following_id = Ash.Changeset.get_attribute(changeset, :following_id)
|
following_id = Ash.Changeset.get_attribute(changeset, :following_id)
|
||||||
@@ -82,10 +65,6 @@ defmodule Mixer.Accounts.Follow do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
identities do
|
|
||||||
identity :unique_follow, [:follower_id, :following_id]
|
|
||||||
end
|
|
||||||
|
|
||||||
policies do
|
policies do
|
||||||
policy action_type(:read) do
|
policy action_type(:read) do
|
||||||
authorize_if always()
|
authorize_if always()
|
||||||
@@ -99,4 +78,27 @@ defmodule Mixer.Accounts.Follow do
|
|||||||
authorize_if actor_present()
|
authorize_if actor_present()
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -31,14 +31,21 @@ defmodule Mixer.Accounts.User.Senders.SendMagicLinkEmail do
|
|||||||
defp body(params) do
|
defp body(params) do
|
||||||
# NOTE: You may have to change this to match your magic link acceptance URL.
|
# NOTE: You may have to change this to match your magic link acceptance URL.
|
||||||
link = url(~p"/magic_link/#{params[:token]}")
|
link = url(~p"/magic_link/#{params[:token]}")
|
||||||
email_template("Your magic link", "Hello, #{params[:email]}!", """
|
|
||||||
|
email_template(
|
||||||
|
"Your magic link",
|
||||||
|
"Hello, #{params[:email]}!",
|
||||||
|
"""
|
||||||
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
<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.
|
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>
|
||||||
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
<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.
|
If you didn't request this, you can safely ignore this email.
|
||||||
</p>
|
</p>
|
||||||
""", link, "Sign In to Mixer")
|
""",
|
||||||
|
link,
|
||||||
|
"Sign In to Mixer"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp email_template(title, greeting, content, button_url, button_label) do
|
defp email_template(title, greeting, content, button_url, button_label) do
|
||||||
|
|||||||
@@ -22,14 +22,21 @@ defmodule Mixer.Accounts.User.Senders.SendNewUserConfirmationEmail do
|
|||||||
|
|
||||||
defp body(params) do
|
defp body(params) do
|
||||||
link = url(~p"/confirm_new_user/#{params[:token]}")
|
link = url(~p"/confirm_new_user/#{params[:token]}")
|
||||||
email_template("Confirm your email", "Welcome to Mixer!", """
|
|
||||||
|
email_template(
|
||||||
|
"Confirm your email",
|
||||||
|
"Welcome to Mixer!",
|
||||||
|
"""
|
||||||
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
<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.
|
Thanks for signing up. Just one more step — confirm your email address to activate your account.
|
||||||
</p>
|
</p>
|
||||||
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
<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.
|
If you didn't create an account on Mixer, you can safely ignore this email.
|
||||||
</p>
|
</p>
|
||||||
""", link, "Confirm Email Address")
|
""",
|
||||||
|
link,
|
||||||
|
"Confirm Email Address"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp email_template(title, greeting, content, button_url, button_label) do
|
defp email_template(title, greeting, content, button_url, button_label) do
|
||||||
|
|||||||
@@ -22,14 +22,21 @@ defmodule Mixer.Accounts.User.Senders.SendPasswordResetEmail do
|
|||||||
|
|
||||||
defp body(params) do
|
defp body(params) do
|
||||||
link = url(~p"/password-reset/#{params[:token]}")
|
link = url(~p"/password-reset/#{params[:token]}")
|
||||||
email_template("Reset your password", "Password reset request", """
|
|
||||||
|
email_template(
|
||||||
|
"Reset your password",
|
||||||
|
"Password reset request",
|
||||||
|
"""
|
||||||
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
<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.
|
We received a request to reset the password for your Mixer account. Click the button below to choose a new one.
|
||||||
</p>
|
</p>
|
||||||
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
<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.
|
If you didn't request a password reset, you can safely ignore this email — your password will not change.
|
||||||
</p>
|
</p>
|
||||||
""", link, "Reset My Password")
|
""",
|
||||||
|
link,
|
||||||
|
"Reset My Password"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp email_template(title, greeting, content, button_url, button_label) do
|
defp email_template(title, greeting, content, button_url, button_label) do
|
||||||
|
|||||||
@@ -3,16 +3,6 @@ defmodule Mixer.Posts do
|
|||||||
otp_app: :mixer,
|
otp_app: :mixer,
|
||||||
extensions: [AshTypescript.Rpc, AshAdmin.Domain]
|
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
|
typescript_rpc do
|
||||||
resource Mixer.Posts.Tweet do
|
resource Mixer.Posts.Tweet do
|
||||||
rpc_action :create_tweet, :create
|
rpc_action :create_tweet, :create
|
||||||
@@ -27,4 +17,14 @@ defmodule Mixer.Posts do
|
|||||||
rpc_action :read_media, :read
|
rpc_action :read_media, :read
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
admin do
|
||||||
|
show? true
|
||||||
|
end
|
||||||
|
|
||||||
|
resources do
|
||||||
|
resource Mixer.Posts.Tweet
|
||||||
|
resource Mixer.Posts.TweetLike
|
||||||
|
resource Mixer.Posts.Media
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ defmodule Mixer.Posts.Media do
|
|||||||
end
|
end
|
||||||
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
|
attributes do
|
||||||
uuid_primary_key :id
|
uuid_primary_key :id
|
||||||
|
|
||||||
@@ -64,22 +82,4 @@ defmodule Mixer.Posts.Media do
|
|||||||
public? true
|
public? true
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ defmodule Mixer.Posts.MediaUploader do
|
|||||||
if ext in @extensions, do: :ok, else: {:error, "unsupported file type #{ext}"}
|
if ext in @extensions, do: :ok, else: {:error, "unsupported file type #{ext}"}
|
||||||
end
|
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
|
def filename(_version, {file, _scope}) do
|
||||||
Path.basename(file.file_name, Path.extname(file.file_name))
|
Path.basename(file.file_name, Path.extname(file.file_name))
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ defmodule Mixer.Posts.Tweet do
|
|||||||
repo Mixer.Repo
|
repo Mixer.Repo
|
||||||
end
|
end
|
||||||
|
|
||||||
typescript do
|
|
||||||
type_name "tweets"
|
|
||||||
end
|
|
||||||
|
|
||||||
state_machine do
|
state_machine do
|
||||||
initial_states [:drafted, :posted]
|
initial_states [:drafted, :posted]
|
||||||
default_initial_state :drafted
|
default_initial_state :drafted
|
||||||
@@ -27,6 +23,10 @@ defmodule Mixer.Posts.Tweet do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
typescript do
|
||||||
|
type_name "tweets"
|
||||||
|
end
|
||||||
|
|
||||||
actions do
|
actions do
|
||||||
defaults [:read, :destroy]
|
defaults [:read, :destroy]
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ defmodule Mixer.Posts.Tweet do
|
|||||||
argument :media_id, :uuid, allow_nil?: true
|
argument :media_id, :uuid, allow_nil?: true
|
||||||
change relate_actor(:user)
|
change relate_actor(:user)
|
||||||
change transition_state(:posted)
|
change transition_state(:posted)
|
||||||
|
|
||||||
change fn changeset, context ->
|
change fn changeset, context ->
|
||||||
case Ash.Changeset.get_argument(changeset, :media_id) do
|
case Ash.Changeset.get_argument(changeset, :media_id) do
|
||||||
nil ->
|
nil ->
|
||||||
@@ -45,7 +46,9 @@ defmodule Mixer.Posts.Tweet do
|
|||||||
Ash.Changeset.after_action(changeset, fn _changeset, tweet ->
|
Ash.Changeset.after_action(changeset, fn _changeset, tweet ->
|
||||||
Mixer.Posts.Media
|
Mixer.Posts.Media
|
||||||
|> Ash.get!(media_id, authorize?: false)
|
|> 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!()
|
|> Ash.update!()
|
||||||
|
|
||||||
{:ok, tweet}
|
{:ok, tweet}
|
||||||
@@ -111,6 +114,32 @@ defmodule Mixer.Posts.Tweet do
|
|||||||
end
|
end
|
||||||
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
|
attributes do
|
||||||
uuid_primary_key :id
|
uuid_primary_key :id
|
||||||
|
|
||||||
@@ -165,32 +194,6 @@ defmodule Mixer.Posts.Tweet do
|
|||||||
end
|
end
|
||||||
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, nil), do: {:error, Ash.Error.Forbidden.exception([])}
|
||||||
|
|
||||||
defp ensure_like(tweet, actor) do
|
defp ensure_like(tweet, actor) do
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ defmodule Mixer.Posts.TweetLike do
|
|||||||
end
|
end
|
||||||
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
|
attributes do
|
||||||
uuid_primary_key :id
|
uuid_primary_key :id
|
||||||
|
|
||||||
@@ -52,18 +66,4 @@ defmodule Mixer.Posts.TweetLike do
|
|||||||
identities do
|
identities do
|
||||||
identity :unique_user_tweet, [:tweet_id, :user_id]
|
identity :unique_user_tweet, [:tweet_id, :user_id]
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ defmodule MixerWeb.PageController do
|
|||||||
use MixerWeb, :controller
|
use MixerWeb, :controller
|
||||||
|
|
||||||
def home(conn, _params) do
|
def home(conn, _params) do
|
||||||
|
if conn.assigns[:current_user] do
|
||||||
|
redirect(conn, to: ~p"/feed")
|
||||||
|
else
|
||||||
render(conn, :home)
|
render(conn, :home)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
render_spa(conn, %{page: "feed", tweet_id: nil, user_id: nil})
|
render_spa(conn, %{page: "feed", tweet_id: nil, user_id: nil})
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<div id="app"
|
<div
|
||||||
|
id="app"
|
||||||
data-current-user-id={if @current_user, do: @current_user.id, else: ""}
|
data-current-user-id={if @current_user, do: @current_user.id, else: ""}
|
||||||
data-current-user-email={if @current_user, do: @current_user.email, else: ""}
|
data-current-user-email={if @current_user, do: @current_user.email, else: ""}
|
||||||
data-asset-host={@media_host}
|
data-asset-host={@media_host}
|
||||||
data-page={@page}
|
data-page={@page}
|
||||||
data-tweet-id={@tweet_id || ""}
|
data-tweet-id={@tweet_id || ""}
|
||||||
data-user-id={@user_id || ""}>
|
data-user-id={@user_id || ""}
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
13
mix.exs
13
mix.exs
@@ -133,18 +133,21 @@ defmodule Mixer.MixProject do
|
|||||||
build: [
|
build: [
|
||||||
"ash-framework": [
|
"ash-framework": [
|
||||||
# The description tells people how to use this skill.
|
# The description tells people how to use this skill.
|
||||||
description: "Use this skill working with Ash Framework or any of its extensions. Always consult this when making any domain changes, features or fixes.",
|
description:
|
||||||
|
"Use this skill working with Ash Framework or any of its extensions. Always consult this when making any domain changes, features or fixes.",
|
||||||
# Include all Ash dependencies
|
# Include all Ash dependencies
|
||||||
usage_rules: [:ash, ~r/^ash_/]
|
usage_rules: [:ash, ~r/^ash_/]
|
||||||
],
|
],
|
||||||
"phoenix-framework": [
|
"phoenix-framework": [
|
||||||
description: "Use this skill working with Phoenix Framework. Consult this when working with the web layer, controllers, views, liveviews etc.",
|
description:
|
||||||
|
"Use this skill working with Phoenix Framework. Consult this when working with the web layer, controllers, views, liveviews etc.",
|
||||||
# Include all Phoenix dependencies
|
# Include all Phoenix dependencies
|
||||||
usage_rules: [:phoenix, ~r/^phoenix_/]
|
usage_rules: [:phoenix, ~r/^phoenix_/]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
[
|
[
|
||||||
file: "AGENTS.md",
|
file: "AGENTS.md",
|
||||||
usage_rules: ["usage_rules:all"],
|
usage_rules: ["usage_rules:all"],
|
||||||
@@ -152,11 +155,13 @@ defmodule Mixer.MixProject do
|
|||||||
location: ".agents/skills",
|
location: ".agents/skills",
|
||||||
build: [
|
build: [
|
||||||
"ash-framework": [
|
"ash-framework": [
|
||||||
description: "Use this skill working with Ash Framework or any of its extensions. Always consult this when making any domain changes, features or fixes.",
|
description:
|
||||||
|
"Use this skill working with Ash Framework or any of its extensions. Always consult this when making any domain changes, features or fixes.",
|
||||||
usage_rules: [:ash, ~r/^ash_/]
|
usage_rules: [:ash, ~r/^ash_/]
|
||||||
],
|
],
|
||||||
"phoenix-framework": [
|
"phoenix-framework": [
|
||||||
description: "Use this skill working with Phoenix Framework. Consult this when working with the web layer, controllers, views, liveviews etc.",
|
description:
|
||||||
|
"Use this skill working with Phoenix Framework. Consult this when working with the web layer, controllers, views, liveviews etc.",
|
||||||
usage_rules: [:phoenix, ~r/^phoenix_/]
|
usage_rules: [:phoenix, ~r/^phoenix_/]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ defmodule Mixer.Repo.Migrations.SetupPostsAndTweets do
|
|||||||
name: "tweets_user_id_fkey",
|
name: "tweets_user_id_fkey",
|
||||||
type: :uuid,
|
type: :uuid,
|
||||||
prefix: "public"
|
prefix: "public"
|
||||||
), null: false
|
),
|
||||||
|
null: false
|
||||||
|
|
||||||
add :state, :text, null: false, default: "drafted"
|
add :state, :text, null: false, default: "drafted"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ defmodule Mixer.Repo.Migrations.AddPostsMediaS3 do
|
|||||||
name: "media_tweet_id_fkey",
|
name: "media_tweet_id_fkey",
|
||||||
type: :uuid,
|
type: :uuid,
|
||||||
prefix: "public"
|
prefix: "public"
|
||||||
), null: false
|
),
|
||||||
|
null: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ defmodule Mixer.Repo.Migrations.AddUserIdToMediaAndAllowNullTweetId do
|
|||||||
name: "media_user_id_fkey",
|
name: "media_user_id_fkey",
|
||||||
type: :uuid,
|
type: :uuid,
|
||||||
prefix: "public"
|
prefix: "public"
|
||||||
), null: false
|
),
|
||||||
|
null: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ defmodule Mixer.Repo.Migrations.AddTweetLikes do
|
|||||||
name: "tweet_likes_tweet_id_fkey",
|
name: "tweet_likes_tweet_id_fkey",
|
||||||
type: :uuid,
|
type: :uuid,
|
||||||
prefix: "public"
|
prefix: "public"
|
||||||
), null: false
|
),
|
||||||
|
null: false
|
||||||
|
|
||||||
add :user_id,
|
add :user_id,
|
||||||
references(:users,
|
references(:users,
|
||||||
@@ -25,7 +26,8 @@ defmodule Mixer.Repo.Migrations.AddTweetLikes do
|
|||||||
name: "tweet_likes_user_id_fkey",
|
name: "tweet_likes_user_id_fkey",
|
||||||
type: :uuid,
|
type: :uuid,
|
||||||
prefix: "public"
|
prefix: "public"
|
||||||
), null: false
|
),
|
||||||
|
null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create unique_index(:tweet_likes, [:tweet_id, :user_id],
|
create unique_index(:tweet_likes, [:tweet_id, :user_id],
|
||||||
|
|||||||
@@ -1,8 +1,29 @@
|
|||||||
defmodule MixerWeb.PageControllerTest do
|
defmodule MixerWeb.PageControllerTest do
|
||||||
use MixerWeb.ConnCase
|
use MixerWeb.ConnCase
|
||||||
|
|
||||||
test "GET /", %{conn: conn} do
|
test "GET / redirects to /feed when logged in", %{conn: conn} do
|
||||||
|
user =
|
||||||
|
Mixer.Accounts.User
|
||||||
|
|> Ash.Changeset.for_create(
|
||||||
|
:register_with_password,
|
||||||
|
%{
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "Password1!",
|
||||||
|
password_confirmation: "Password1!"
|
||||||
|
}, authorize?: false)
|
||||||
|
|> Ash.create!()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> Plug.Test.init_test_session(%{})
|
||||||
|
|> AshAuthentication.Plug.Helpers.store_in_session(user)
|
||||||
|
|> get(~p"/")
|
||||||
|
|
||||||
|
assert redirected_to(conn) == ~p"/feed"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET / renders the home page for unauthenticated users", %{conn: conn} do
|
||||||
conn = get(conn, ~p"/")
|
conn = get(conn, ~p"/")
|
||||||
assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
|
assert html_response(conn, 200) =~ "Mixer"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user