From 83b63bbd4b901422950eea38f5772461453e3e0d Mon Sep 17 00:00:00 2001 From: "byte[]" Date: Fri, 4 Oct 2019 18:52:44 -0400 Subject: [PATCH] add topics and channels strips --- lib/philomena/channels.ex | 200 ++++++++++++++++++ lib/philomena/channels/channel.ex | 39 ++++ lib/philomena/channels/subscription.ex | 18 ++ lib/philomena/topics/topic.ex | 1 + .../controllers/activity_controller.ex | 25 ++- .../activity/_channel_strip.html.slime | 18 ++ .../activity/_topic_strip.html.slime | 13 ++ .../templates/activity/index.html.slime | 20 +- .../templates/image/_image_box.html.slime | 15 +- .../templates/image/index.html.slime | 2 +- test/philomena/channels_test.exs | 119 +++++++++++ 11 files changed, 447 insertions(+), 23 deletions(-) create mode 100644 lib/philomena/channels.ex create mode 100644 lib/philomena/channels/channel.ex create mode 100644 lib/philomena/channels/subscription.ex create mode 100644 lib/philomena_web/templates/activity/_channel_strip.html.slime create mode 100644 lib/philomena_web/templates/activity/_topic_strip.html.slime create mode 100644 test/philomena/channels_test.exs diff --git a/lib/philomena/channels.ex b/lib/philomena/channels.ex new file mode 100644 index 00000000..2d8a96db --- /dev/null +++ b/lib/philomena/channels.ex @@ -0,0 +1,200 @@ +defmodule Philomena.Channels do + @moduledoc """ + The Channels context. + """ + + import Ecto.Query, warn: false + alias Philomena.Repo + + alias Philomena.Channels.Channel + + @doc """ + Returns the list of channels. + + ## Examples + + iex> list_channels() + [%Channel{}, ...] + + """ + def list_channels do + Repo.all(Channel) + end + + @doc """ + Gets a single channel. + + Raises `Ecto.NoResultsError` if the Channel does not exist. + + ## Examples + + iex> get_channel!(123) + %Channel{} + + iex> get_channel!(456) + ** (Ecto.NoResultsError) + + """ + def get_channel!(id), do: Repo.get!(Channel, id) + + @doc """ + Creates a channel. + + ## Examples + + iex> create_channel(%{field: value}) + {:ok, %Channel{}} + + iex> create_channel(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_channel(attrs \\ %{}) do + %Channel{} + |> Channel.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a channel. + + ## Examples + + iex> update_channel(channel, %{field: new_value}) + {:ok, %Channel{}} + + iex> update_channel(channel, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_channel(%Channel{} = channel, attrs) do + channel + |> Channel.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Channel. + + ## Examples + + iex> delete_channel(channel) + {:ok, %Channel{}} + + iex> delete_channel(channel) + {:error, %Ecto.Changeset{}} + + """ + def delete_channel(%Channel{} = channel) do + Repo.delete(channel) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking channel changes. + + ## Examples + + iex> change_channel(channel) + %Ecto.Changeset{source: %Channel{}} + + """ + def change_channel(%Channel{} = channel) do + Channel.changeset(channel, %{}) + end + + alias Philomena.Channels.Subscription + + @doc """ + Returns the list of channel_subscriptions. + + ## Examples + + iex> list_channel_subscriptions() + [%Subscription{}, ...] + + """ + def list_channel_subscriptions do + Repo.all(Subscription) + end + + @doc """ + Gets a single subscription. + + Raises `Ecto.NoResultsError` if the Subscription does not exist. + + ## Examples + + iex> get_subscription!(123) + %Subscription{} + + iex> get_subscription!(456) + ** (Ecto.NoResultsError) + + """ + def get_subscription!(id), do: Repo.get!(Subscription, id) + + @doc """ + Creates a subscription. + + ## Examples + + iex> create_subscription(%{field: value}) + {:ok, %Subscription{}} + + iex> create_subscription(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_subscription(attrs \\ %{}) do + %Subscription{} + |> Subscription.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a subscription. + + ## Examples + + iex> update_subscription(subscription, %{field: new_value}) + {:ok, %Subscription{}} + + iex> update_subscription(subscription, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_subscription(%Subscription{} = subscription, attrs) do + subscription + |> Subscription.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Subscription. + + ## Examples + + iex> delete_subscription(subscription) + {:ok, %Subscription{}} + + iex> delete_subscription(subscription) + {:error, %Ecto.Changeset{}} + + """ + def delete_subscription(%Subscription{} = subscription) do + Repo.delete(subscription) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking subscription changes. + + ## Examples + + iex> change_subscription(subscription) + %Ecto.Changeset{source: %Subscription{}} + + """ + def change_subscription(%Subscription{} = subscription) do + Subscription.changeset(subscription, %{}) + end +end diff --git a/lib/philomena/channels/channel.ex b/lib/philomena/channels/channel.ex new file mode 100644 index 00000000..6ce5b3c8 --- /dev/null +++ b/lib/philomena/channels/channel.ex @@ -0,0 +1,39 @@ +defmodule Philomena.Channels.Channel do + use Ecto.Schema + import Ecto.Changeset + + schema "channels" do + belongs_to :associated_artist_tag, Philomena.Tags.Tag + + # fixme: rails STI + field :type, :string + + field :short_name, :string + field :title, :string + field :description, :string + field :tags, :string + field :viewers, :integer, default: 0 + field :nsfw, :boolean, default: false + field :is_live, :boolean, default: false + field :last_fetched_at, :naive_datetime + field :next_check_at, :naive_datetime + field :last_live_at, :naive_datetime + + field :viewer_minutes_today, :integer, default: 0 + field :viewer_minutes_thisweek, :integer, default: 0 + field :viewer_minutes_thismonth, :integer, default: 0 + field :total_viewer_minutes, :integer, default: 0 + + field :banner_image, :string + field :channel_image, :string + field :remote_stream_id, :integer + field :thumbnail_url, :string, default: "" + end + + @doc false + def changeset(channel, attrs) do + channel + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/channels/subscription.ex b/lib/philomena/channels/subscription.ex new file mode 100644 index 00000000..9c4b57e9 --- /dev/null +++ b/lib/philomena/channels/subscription.ex @@ -0,0 +1,18 @@ +defmodule Philomena.Channels.Subscription do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + schema "channel_subscriptions" do + belongs_to :channel, Philomena.Channels.Channel, primary_key: true + belongs_to :user, Philomena.Users.User, primary_key: true + end + + @doc false + def changeset(subscription, attrs) do + subscription + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/topics/topic.ex b/lib/philomena/topics/topic.ex index de74f1ee..b82bba19 100644 --- a/lib/philomena/topics/topic.ex +++ b/lib/philomena/topics/topic.ex @@ -7,6 +7,7 @@ defmodule Philomena.Topics.Topic do belongs_to :deleted_by, Philomena.Users.User belongs_to :locked_by, Philomena.Users.User belongs_to :last_post, Philomena.Posts.Post + belongs_to :forum, Philomena.Forums.Forum field :title, :string field :post_count, :integer, default: 0 diff --git a/lib/philomena_web/controllers/activity_controller.ex b/lib/philomena_web/controllers/activity_controller.ex index e97de1de..be75a3ca 100644 --- a/lib/philomena_web/controllers/activity_controller.ex +++ b/lib/philomena_web/controllers/activity_controller.ex @@ -21,6 +21,7 @@ defmodule PhilomenaWeb.ActivityController do must: image_query } }, + size: 25, sort: %{created_at: :desc} }, Image |> preload([:tags]) @@ -42,8 +43,8 @@ defmodule PhilomenaWeb.ActivityController do Image |> preload([:tags]) ) - watched = if user do - {:ok, watched_query} = if !!user, do: Images.Query.compile(user, "my:watched") + watched = if !!user do + {:ok, watched_query} = Images.Query.compile(user, "my:watched") Image.search_records( %{ @@ -53,6 +54,7 @@ defmodule PhilomenaWeb.ActivityController do must: watched_query } }, + size: 25, sort: %{created_at: :desc} }, Image |> preload([:tags]) @@ -66,23 +68,24 @@ defmodule PhilomenaWeb.ActivityController do |> limit(1) |> Repo.one() - #streams = - # Channel - # |> where([c], c.nsfw == false) - # |> where([c], not is_nil(c.last_fetched_at)) - # |> order_by(desc: :is_live, asc: :title) - # |> limit(6) - # |> Repo.all() + streams = + Channel + |> where([c], c.nsfw == false) + |> where([c], not is_nil(c.last_fetched_at)) + |> order_by(desc: :is_live, asc: :title) + |> limit(6) + |> Repo.all() topics = Topic - |> join(:inner, [t], f in Forum, on: [forum_id: f.id]) + |> join(:inner, [t], f in Forum, on: [id: t.forum_id]) |> where([t, _f], t.hidden_from_users == false) |> where([t, _f], not ilike(t.title, "NSFW")) |> where([_t, f], f.access_level == "normal") |> order_by(desc: :last_replied_to_at) |> preload([:forum, last_post: :user]) |> limit(6) + |> Repo.all() render( conn, @@ -91,7 +94,7 @@ defmodule PhilomenaWeb.ActivityController do top_scoring: top_scoring, watched: watched, featured_image: featured_image, - #streams: streams, + streams: streams, topics: topics ) end diff --git a/lib/philomena_web/templates/activity/_channel_strip.html.slime b/lib/philomena_web/templates/activity/_channel_strip.html.slime new file mode 100644 index 00000000..8baf765b --- /dev/null +++ b/lib/philomena_web/templates/activity/_channel_strip.html.slime @@ -0,0 +1,18 @@ +.block__content.flex.alternating-color + .flex__grow + / TODO + a href="/" + /- if channel.channel_image.present? + / => image_tag channel.uploaded_channel_image.url, width: 32, height: 32, alt: "#{channel.title}'s logo'", class: 'channel-strip__image' + /- else + / => image_tag 'no_avatar_original.svg', width: 32, height: 32, alt: "#{channel.title}'s logo'", class: 'channel-strip__image' + = @channel.title || @channel.short_name + .flex__fixed.flex__right + = if @channel.is_live do + span.channel-strip__state.label.label--narrow.label--success + ' LIVE NOW + br + = if @channel.viewers == 1, do: "viewer", else: "viewers" + - else + span.channel-strip__state.label.label--narrow.label--danger + ' OFF AIR \ No newline at end of file diff --git a/lib/philomena_web/templates/activity/_topic_strip.html.slime b/lib/philomena_web/templates/activity/_topic_strip.html.slime new file mode 100644 index 00000000..a2326c84 --- /dev/null +++ b/lib/philomena_web/templates/activity/_topic_strip.html.slime @@ -0,0 +1,13 @@ +.block__content.alternating-color + = if @topic.sticky do + i.fa.fa-thumbtack> + = if @topic.last_post do + span.hyphenate-breaks + = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @topic.last_post + ' replied to + /=> link_to 'replied to', short_topic_post_path(topic.forum, topic, topic.last_post, anchor: "post_#{topic.last_post.id}") TODO + => @topic.title + /=<> link_to topic.title, short_topic_path(topic.forum, topic) + ' in + => @topic.forum.name + /=< link_to topic.forum.name, short_forum_path(topic.forum) diff --git a/lib/philomena_web/templates/activity/index.html.slime b/lib/philomena_web/templates/activity/index.html.slime index 16561f1d..0276f7c5 100644 --- a/lib/philomena_web/templates/activity/index.html.slime +++ b/lib/philomena_web/templates/activity/index.html.slime @@ -3,7 +3,7 @@ = if @featured_image do .center h4.remove-top-margin Featured Image - = render PhilomenaWeb.ImageView, "index.html", images: [@featured_image] + = render PhilomenaWeb.ImageView, "_image_box.html", image: @featured_image, size: :medium .block.block--fixed.block--fixed--sub.block--success.center.hide-mobile ' Enjoy the site? a href="/pages/donations" @@ -13,17 +13,20 @@ a< href="/pages/contact" Contact us! .block.hide-mobile .block__content.flex.flex--centered.flex--wrap.image-flex-grid - = render PhilomenaWeb.ImageView, "index.html", images: @top_scoring - /a.block__header--single-item.center href=search_path(q: '*', sf: 'score', sd: 'desc') All Time Top Scoring - /a.block__header--single-item.center href='/lists' More Lists + = for image <- @top_scoring do + = render PhilomenaWeb.ImageView, "_image_box.html", image: image, size: :thumb_small + a.block__header--single-item.center href="/search?q=*&sf=score&sd=desc" + ' All Time Top Scoring .block.hide-mobile a.block__header--single-item.center href="/channels" ' Streams - /= render partial: 'channels/channel_strip', collection: @streams + = for channel <- @streams do + = render PhilomenaWeb.ActivityView, "_channel_strip.html", channel: channel .block.hide-mobile a.block__header--single-item.center href="/forums" ' Forum Activity - /= render partial: 'topics/topic_slim', collection: @topics + = for topic <- @topics do + = render PhilomenaWeb.ActivityView, "_topic_strip.html", topic: topic .block.hide-mobile a.block__header--single-item.center href="/lists/recent_comments" ' Recent Comments @@ -31,8 +34,7 @@ /a.block__header--single-item.center href=search_path(q: 'first_seen_at.gt:3 days ago', sf: 'comments', sd: 'desc') Most Commented-on Images .column-layout__main - /- @pagination_params = { params: { controller: :images, action: :index } } - = render PhilomenaWeb.ImageView, "index.html", images: @images + = render PhilomenaWeb.ImageView, "index.html", images: @images, size: :thumb = if !!@watched and @watched != [] do .block .block__header @@ -43,4 +45,4 @@ span.hide-mobile ' Browse Watched Images .block__content.js-resizable-media-container - = render PhilomenaWeb.ImageView, "index.html", images: @watched + = render PhilomenaWeb.ImageView, "index.html", images: @watched, size: :thumb_small diff --git a/lib/philomena_web/templates/image/_image_box.html.slime b/lib/philomena_web/templates/image/_image_box.html.slime index 690034d7..66d56337 100644 --- a/lib/philomena_web/templates/image/_image_box.html.slime +++ b/lib/philomena_web/templates/image/_image_box.html.slime @@ -1,3 +1,14 @@ +elixir: + size = + case @size do + :thumb -> + "media-box__content--large" + :medium -> + "media-box__content--featured" + _ -> + "media-box__content--small" + end + .media-box data-image-id=@image.id .media-box__header.media-box__header--link-row data-image-id=@image.id a.interaction--fave href="#" rel="nofollow" data-image-id=@image.id @@ -14,5 +25,5 @@ span.comments_count data-image-id=@image.id = @image.comments_count a.interaction--hide href="#" rel="nofollow" data-image-id=@image.id i.fa.fa-eye-slash title='Hide' - .media-box__content.media-box__content--large.flex.flex--centered.flex--center-distributed - = render PhilomenaWeb.ImageView, "_image_container.html", image: @image, size: :thumb \ No newline at end of file + .media-box__content.flex.flex--centered.flex--center-distributed class=size + = render PhilomenaWeb.ImageView, "_image_container.html", image: @image, size: @size \ No newline at end of file diff --git a/lib/philomena_web/templates/image/index.html.slime b/lib/philomena_web/templates/image/index.html.slime index 3084ef11..f98db93c 100644 --- a/lib/philomena_web/templates/image/index.html.slime +++ b/lib/philomena_web/templates/image/index.html.slime @@ -1,4 +1,4 @@ .block#imagelist-container .block__content.js-resizable-media-container = for image <- @images do - = render PhilomenaWeb.ImageView, "_image_box.html", image: image + = render PhilomenaWeb.ImageView, "_image_box.html", image: image, size: assigns[:size] || :thumb \ No newline at end of file diff --git a/test/philomena/channels_test.exs b/test/philomena/channels_test.exs new file mode 100644 index 00000000..52395052 --- /dev/null +++ b/test/philomena/channels_test.exs @@ -0,0 +1,119 @@ +defmodule Philomena.ChannelsTest do + use Philomena.DataCase + + alias Philomena.Channels + + describe "channels" do + alias Philomena.Channels.Channel + + @valid_attrs %{} + @update_attrs %{} + @invalid_attrs %{} + + def channel_fixture(attrs \\ %{}) do + {:ok, channel} = + attrs + |> Enum.into(@valid_attrs) + |> Channels.create_channel() + + channel + end + + test "list_channels/0 returns all channels" do + channel = channel_fixture() + assert Channels.list_channels() == [channel] + end + + test "get_channel!/1 returns the channel with given id" do + channel = channel_fixture() + assert Channels.get_channel!(channel.id) == channel + end + + test "create_channel/1 with valid data creates a channel" do + assert {:ok, %Channel{} = channel} = Channels.create_channel(@valid_attrs) + end + + test "create_channel/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Channels.create_channel(@invalid_attrs) + end + + test "update_channel/2 with valid data updates the channel" do + channel = channel_fixture() + assert {:ok, %Channel{} = channel} = Channels.update_channel(channel, @update_attrs) + end + + test "update_channel/2 with invalid data returns error changeset" do + channel = channel_fixture() + assert {:error, %Ecto.Changeset{}} = Channels.update_channel(channel, @invalid_attrs) + assert channel == Channels.get_channel!(channel.id) + end + + test "delete_channel/1 deletes the channel" do + channel = channel_fixture() + assert {:ok, %Channel{}} = Channels.delete_channel(channel) + assert_raise Ecto.NoResultsError, fn -> Channels.get_channel!(channel.id) end + end + + test "change_channel/1 returns a channel changeset" do + channel = channel_fixture() + assert %Ecto.Changeset{} = Channels.change_channel(channel) + end + end + + describe "channel_subscriptions" do + alias Philomena.Channels.Subscription + + @valid_attrs %{} + @update_attrs %{} + @invalid_attrs %{} + + def subscription_fixture(attrs \\ %{}) do + {:ok, subscription} = + attrs + |> Enum.into(@valid_attrs) + |> Channels.create_subscription() + + subscription + end + + test "list_channel_subscriptions/0 returns all channel_subscriptions" do + subscription = subscription_fixture() + assert Channels.list_channel_subscriptions() == [subscription] + end + + test "get_subscription!/1 returns the subscription with given id" do + subscription = subscription_fixture() + assert Channels.get_subscription!(subscription.id) == subscription + end + + test "create_subscription/1 with valid data creates a subscription" do + assert {:ok, %Subscription{} = subscription} = Channels.create_subscription(@valid_attrs) + end + + test "create_subscription/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Channels.create_subscription(@invalid_attrs) + end + + test "update_subscription/2 with valid data updates the subscription" do + subscription = subscription_fixture() + assert {:ok, %Subscription{} = subscription} = Channels.update_subscription(subscription, @update_attrs) + end + + test "update_subscription/2 with invalid data returns error changeset" do + subscription = subscription_fixture() + assert {:error, %Ecto.Changeset{}} = Channels.update_subscription(subscription, @invalid_attrs) + assert subscription == Channels.get_subscription!(subscription.id) + end + + test "delete_subscription/1 deletes the subscription" do + subscription = subscription_fixture() + assert {:ok, %Subscription{}} = Channels.delete_subscription(subscription) + assert_raise Ecto.NoResultsError, fn -> Channels.get_subscription!(subscription.id) end + end + + test "change_subscription/1 returns a subscription changeset" do + subscription = subscription_fixture() + assert %Ecto.Changeset{} = Channels.change_subscription(subscription) + end + end +end