diff --git a/Gemfile b/Gemfile index 13d40d95e..01985fb6a 100644 --- a/Gemfile +++ b/Gemfile @@ -58,6 +58,7 @@ gem "jquery-rails", "~> 4.3" gem "kaminari", "~> 1.1" gem "libhoney", "~> 1.11" gem "liquid", "~> 4.0" +gem "ffprober", "~> 0.5" gem "nokogiri", "~> 1.10" gem "octokit", "~> 4.13" gem "omniauth", "~> 1.9" diff --git a/Gemfile.lock b/Gemfile.lock index a91dd3422..5f3cf6495 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -311,6 +311,7 @@ GEM loofah (>= 2.0) sax-machine (>= 1.0) ffi (1.9.25) + ffprober (0.5.3) figaro (1.1.1) thor (~> 0.14) fission (0.5.0) @@ -979,6 +980,7 @@ DEPENDENCIES fastly (~> 1.15) fastly-rails (~> 0.8) feedjira (~> 2.2) + ffprober (~> 0.5) figaro (~> 1.1) fix-db-schema-conflicts! fog (~> 1.41) diff --git a/app/assets/images/video-camera.svg b/app/assets/images/video-camera.svg new file mode 100644 index 000000000..12af84124 --- /dev/null +++ b/app/assets/images/video-camera.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/utilities/buildArticleHTML.js.erb b/app/assets/javascripts/utilities/buildArticleHTML.js.erb index a625dd09e..b2c374514 100644 --- a/app/assets/javascripts/utilities/buildArticleHTML.js.erb +++ b/app/assets/javascripts/utilities/buildArticleHTML.js.erb @@ -98,7 +98,12 @@ function buildArticleHTML(article) { } else if (article.class_name === "Article") { readingTimeHTML = '3 min read' } + var videoHTML = ''; + if (article.cloudinary_video_url) { + videoHTML = '
" />'+article.video_duration_in_minutes+'
' + } return '
\ + '+videoHTML+'\ '+orgHeadline+'\
\ \ diff --git a/app/assets/stylesheets/articles.scss b/app/assets/stylesheets/articles.scss index df65918a2..54bb2e55d 100644 --- a/app/assets/stylesheets/articles.scss +++ b/app/assets/stylesheets/articles.scss @@ -233,6 +233,36 @@ overflow: hidden; text-overflow: ellipsis; } + .single-article-video-preview { + img { + width: 100%; + } + position: relative; + padding-top: 56%; + display: block; + background: $black no-repeat center center; + background-size: cover; + &:hover { + opacity: 1; + } + .single-article-video-duration { + position: absolute; + bottom: 8px; + right: 7px; + background: rgba(0,0,0,0.8); + color: white; + border-radius: 3px; + padding: 2px 5px 3px; + font-size: 0.75em; + font-weight: 500; + img { + width: 12px; + height: 12px; + margin-right: 4px; + vertical-align: -1px; + } + } + } .article-organization-headline { display:block; margin-bottom:calc(-1px - 0.6vw); diff --git a/app/controllers/internal/users_controller.rb b/app/controllers/internal/users_controller.rb index 52b265df5..ac2fd0fff 100644 --- a/app/controllers/internal/users_controller.rb +++ b/app/controllers/internal/users_controller.rb @@ -55,22 +55,12 @@ class Internal::UsersController < Internal::ApplicationController toggle_warn_user if user_params[:warn_user] toggle_trust_user if user_params[:trusted_user] toggle_ban_from_mentorship if user_params[:ban_from_mentorship] - toggle_video_permission if user_params[:video_permission] - end - - def toggle_video_permission - if user_params[:video_permission] == "1" - @user.add_role :video_permission - else - @user.remove_role :video_permission - end end def toggle_ban_user if user_params[:ban_user] == "1" @user.add_role :banned @user.remove_role :trusted - @user.remove_role :video_permission create_note("banned", user_params[:note_for_current_role]) else @user.remove_role :banned @@ -189,7 +179,6 @@ class Internal::UsersController < Internal::ApplicationController :note_for_mentorship_ban, :note_for_current_role, :reason_for_mentorship_ban, - :trusted_user, - :video_permission) + :trusted_user) end end diff --git a/app/models/article.rb b/app/models/article.rb index 91b1a53b2..3336e470a 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -49,6 +49,7 @@ class Article < ApplicationRecord before_save :set_all_dates before_save :calculate_base_scores before_save :set_caches + before_save :fetch_video_duration after_save :async_score_calc, if: :published after_save :bust_cache after_save :update_main_image_background_hex @@ -75,7 +76,7 @@ class Article < ApplicationRecord :main_image, :main_image_background_hex_color, :updated_at, :slug, :video, :user_id, :organization_id, :video_source_url, :video_code, :video_thumbnail_url, :video_closed_caption_track_url, - :published_at, :crossposted_at, :boost_states, :description, :reading_time) + :published_at, :crossposted_at, :boost_states, :description, :reading_time, :video_duration_in_seconds) } scope :limited_columns_internal_select, -> { @@ -137,7 +138,8 @@ class Article < ApplicationRecord enqueue: :trigger_delayed_index do attributes :title, :path, :class_name, :comments_count, :reading_time, :tag_list, :positive_reactions_count, :id, :hotness_score, :score, - :readable_publish_date, :flare_tag, :user_id, :organization_id + :readable_publish_date, :flare_tag, :user_id, :organization_id, + :cloudinary_video_url, :video_duration_in_minutes attribute :published_at_int do published_at.to_i end @@ -384,6 +386,25 @@ class Article < ApplicationRecord user&.collections&.pluck(:slug) end + def cloudinary_video_url + ApplicationController.helpers.cloudinary(video_thumbnail_url, 880) + end + + def video_duration_in_minutes + minutes = (video_duration_in_seconds.to_i / 60) % 60 + seconds = video_duration_in_seconds.to_i % 60 + "#{minutes}:#{seconds}" + end + + def fetch_video_duration + if video.present? && video_duration_in_seconds.zero? + info = Ffprober::Parser.from_url video + self.video_duration_in_seconds = info.json[:format][:duration].to_f + end + rescue StandardError => e + puts e.message + end + private def update_notifications @@ -454,7 +475,7 @@ class Article < ApplicationRecord if published && video_state == "PROGRESSING" return errors.add(:published, "cannot be set to true if video is still processing") end - if video.present? && !user.has_role?(:video_permission) + if video.present? && user.created_at > 2.weeks.ago return errors.add(:video, "cannot be added member without permission") end end diff --git a/app/models/role.rb b/app/models/role.rb index 791388745..77dcc1322 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -25,7 +25,6 @@ class Role < ApplicationRecord level_2_member level_1_member workshop_pass - video_permission chatroom_beta_tester banned_from_mentorship comment_banned diff --git a/app/models/user.rb b/app/models/user.rb index c1c114f74..a1e349296 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,7 @@ class User < ApplicationRecord attr_accessor :scholar_email, :note, :ban_from_mentorship, :quick_match, :ban_user, :warn_user, :note_for_mentorship_ban, :reason_for_mentorship_ban, - :note_for_current_role, :add_mentor, :add_mentee, :trusted_user, :video_permission + :note_for_current_role, :add_mentor, :add_mentee, :trusted_user rolify include AlgoliaSearch diff --git a/app/policies/video_policy.rb b/app/policies/video_policy.rb index d5c5b4395..353d6c345 100644 --- a/app/policies/video_policy.rb +++ b/app/policies/video_policy.rb @@ -1,9 +1,9 @@ class VideoPolicy < ApplicationPolicy def new? - user.has_role?(:video_permission) + user.created_at < 2.weeks.ago if user.created_at end def create? - user.has_role?(:video_permission) + user.created_at < 2.weeks.ago if user.created_at end end diff --git a/app/views/articles/_single_story.html.erb b/app/views/articles/_single_story.html.erb index dbedd05d8..15150c777 100644 --- a/app/views/articles/_single_story.html.erb +++ b/app/views/articles/_single_story.html.erb @@ -1,13 +1,15 @@
- <% if story.video.present? %> - <%= render "articles/video_player", meta_tags: false, article: story %> + <% if story.video.present? && story.video_thumbnail_url.present? %> + +
" /><%= story.video_duration_in_minutes %>
+
<% end %> <% organization = story.organization %> <% if organization && !@organization_article_index %>
<%= organization.name %> 
<% end %> - +
<% if story.cached_tag_list_array.include?("hiring") && story.organization_id.present? && !@organization_article_index %> <%= story.organization.username %> profile @@ -16,7 +18,7 @@ <% end %>
- +

<%= render "articles/tag_identifier", story: story, tag: @tag %> @@ -24,7 +26,7 @@

- <%= story.user.name %>・<%= story.readable_publish_date %> + <%= story.user.name %>・<%= story.readable_publish_date %>

@@ -34,13 +36,13 @@
<% if story.comments_count > 0 %>
- + " alt="chat" />
<% end %>
- + " alt="Reactions" /> diff --git a/app/views/articles/_video_player.html.erb b/app/views/articles/_video_player.html.erb index e39009d3b..3fcd0f5ad 100644 --- a/app/views/articles/_video_player.html.erb +++ b/app/views/articles/_video_player.html.erb @@ -1,10 +1,11 @@ -
class="video-player-header"> +
<% if meta_tags %> + " /> <% end %>
@@ -40,6 +41,7 @@ playerInstance.setup({ file: "<%= article.video_source_url %>", mediaid: "<%= article.video_code %>", + autostart: <%= internal_navigation? %>, image: "<%= cloudinary(article.video_thumbnail_url, 880) %>", playbackRateControls: true, tracks: [{ diff --git a/app/views/dashboards/show.html.erb b/app/views/dashboards/show.html.erb index ab8803613..15d359ce1 100644 --- a/app/views/dashboards/show.html.erb +++ b/app/views/dashboards/show.html.erb @@ -2,15 +2,15 @@
- " href="/dashboard"> + " href="/dashboard"> POSTS (<%= @user.articles_count %>) - " href="/dashboard/user_followers"> + " href="/dashboard/user_followers"> FOLLOWERS (<%= @user.followers_count %>) - " href="/dashboard/following"> + " href="/dashboard/following"> FOLLOWING (<%= @user.following_users_count + @user.following_tags_count %>) @@ -78,7 +78,7 @@ <% end %> <% elsif @articles.any? %> <%= render "analytics" %> - <% if current_user.has_role?(:video_permission) %> + <% if current_user.created_at < 2.weeks.ago %> Upload a Video 🎥 diff --git a/app/views/internal/users/show.html.erb b/app/views/internal/users/show.html.erb index fa2d2151e..296ac63d7 100644 --- a/app/views/internal/users/show.html.erb +++ b/app/views/internal/users/show.html.erb @@ -52,8 +52,6 @@ <%= form_for [:internal, @user] do |f| %>

Add Trusted Role (Community Moderator) <%= f.check_box :trusted_user, checked: @user.trusted %>

-

Allow Video Uploads - <%= f.check_box :video_permission, checked: @user.has_role?(:video_permission) %>

<%= f.submit "Update Privileges" %> <% end %> <% else %> diff --git a/app/views/videos/new.html.erb b/app/views/videos/new.html.erb index c35072870..1278b103c 100644 --- a/app/views/videos/new.html.erb +++ b/app/views/videos/new.html.erb @@ -18,7 +18,7 @@ key: "video-upload__#{SecureRandom.hex}", key_starts_with: "video-upload__", acl: "public-read", - max_file_size: (current_user.has_role?(:super_admin) ? 15000 : 3500).megabytes, + max_file_size: (current_user.has_role?(:super_admin) ? 20000 : 6000).megabytes, id: "s3-uploader", class: "upload-form", data: {:key => :val} do %> diff --git a/db/migrate/20190216185753_add_video_duration_in_seconds_to_articles.rb b/db/migrate/20190216185753_add_video_duration_in_seconds_to_articles.rb new file mode 100644 index 000000000..a311e1ed3 --- /dev/null +++ b/db/migrate/20190216185753_add_video_duration_in_seconds_to_articles.rb @@ -0,0 +1,5 @@ +class AddVideoDurationInSecondsToArticles < ActiveRecord::Migration[5.1] + def change + add_column :articles, :video_duration_in_seconds, :float, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 967122266..9b0bc7a46 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190206222055) do +ActiveRecord::Schema.define(version: 20190216185753) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -117,6 +117,7 @@ ActiveRecord::Schema.define(version: 20190206222055) do t.string "video" t.string "video_closed_caption_track_url" t.string "video_code" + t.float "video_duration_in_seconds", default: 0.0 t.string "video_source_url" t.string "video_state" t.string "video_thumbnail_url" diff --git a/spec/factories/articles.rb b/spec/factories/articles.rb index d8381fcd5..377ba44c1 100644 --- a/spec/factories/articles.rb +++ b/spec/factories/articles.rb @@ -37,8 +37,7 @@ FactoryBot.define do trait :video do after(:build) do |article| - article.video = "https://video.com" - article.user.add_role :video_permission + article.video = "https://s3.amazonaws.com/dev-to-input-v0/video-upload__2d7dc29e39a40c7059572bca75bb646b" article.save end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index d532d18db..18bf58d84 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -39,7 +39,7 @@ FactoryBot.define do end trait :video_permission do - after(:build) { |user| user.add_role :video_permission } + after(:build) { |user| user.created_at = 3.weeks.ago } end trait :ignore_after_callback do diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb index bc1b6c532..28b39655e 100644 --- a/spec/models/article_spec.rb +++ b/spec/models/article_spec.rb @@ -206,7 +206,7 @@ RSpec.describe Article, type: :model do describe "#video" do it "must be a url" do - article.user.add_role(:video_permission) + article.user.created_at = 3.weeks.ago article.video = "hey" expect(article).not_to be_valid article.video = "http://hey.com" @@ -216,7 +216,14 @@ RSpec.describe Article, type: :model do it "must belong to permissioned user" do article.video = "http://hey.com" expect(article).not_to be_valid - article.user.add_role(:video_permission) + article.user.created_at = 3.weeks.ago + expect(article).to be_valid + end + + it "saves with video" do + article.user.created_at = 3.weeks.ago + article.video = "https://s3.amazonaws.com/dev-to-input-v0/video-upload__2d7dc29e39a40c7059572bca75bb646b" + article.save expect(article).to be_valid end end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index 5fc514a74..c8da15c9a 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe Comment, type: :model do - let(:user) { create(:user) } + let(:user) { create(:user, created_at: 3.weeks.ago) } let(:user2) { create(:user) } let(:article) { create(:article, user_id: user.id, published: true) } let(:article_with_video) { create(:article, :video, user_id: user.id, published: true) } diff --git a/spec/policies/video_policy_spec.rb b/spec/policies/video_policy_spec.rb index 2343e29f2..b0ba3e2cf 100644 --- a/spec/policies/video_policy_spec.rb +++ b/spec/policies/video_policy_spec.rb @@ -17,10 +17,10 @@ RSpec.describe VideoPolicy do it { is_expected.to forbid_actions(%i[new create]) } end - context "when user does not have video permission" do + context "when does have video permission" do let(:user) { build(:user) } - before { user.add_role :video_permission } + before { user.created_at = 3.weeks.ago } it { is_expected.to permit_actions(%i[new create]) } end