Do not autoplay videos, display play button instead. Use expiring links when using S3. Do not keep originals

for avatars/headers, resize avatars down to 120x120 instead of 300x300. Set cache headers on S3 stuff, also
make it private (aka only accessible via expiring links to prevent hotlinking)
This commit is contained in:
Eugen Rochko 2016-12-04 12:26:12 +01:00
parent 290ffb63cd
commit 80c44ed9c1
18 changed files with 34 additions and 27 deletions

View file

@ -53,7 +53,8 @@ const VideoPlayer = React.createClass({
propTypes: { propTypes: {
media: ImmutablePropTypes.map.isRequired, media: ImmutablePropTypes.map.isRequired,
width: React.PropTypes.number, width: React.PropTypes.number,
height: React.PropTypes.number height: React.PropTypes.number,
sensitive: React.PropTypes.bool
}, },
getDefaultProps () { getDefaultProps () {
@ -102,6 +103,12 @@ const VideoPlayer = React.createClass({
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> <span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</div> </div>
); );
} else if (!sensitive && !this.state.visible) {
return (
<div style={{ cursor: 'pointer', position: 'relative', marginTop: '8px', width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }} onClick={this.handleOpen}>
<div style={{ position: 'absolute', top: '50%', left: '50%', fontSize: '36px', transform: 'translate(-50%, -50%)', padding: '5px', borderRadius: '100px', color: 'rgba(255, 255, 255, 0.8)' }}><i className='fa fa-play' /></div>
</div>
);
} }
return ( return (

View file

@ -112,13 +112,11 @@ module AtomBuilderHelper
end end
def link_enclosure(xml, media) def link_enclosure(xml, media)
xml.link(rel: 'enclosure', href: full_asset_url(media.file.url), type: media.file_content_type, length: media.file_file_size) xml.link(rel: 'enclosure', href: full_asset_url(media.file.url(:original, false)), type: media.file_content_type, length: media.file_file_size)
end end
def link_avatar(xml, account) def link_avatar(xml, account)
single_link_avatar(xml, account, :large, 300) single_link_avatar(xml, account, :original, 120)
# single_link_avatar(xml, account, :medium, 96)
# single_link_avatar(xml, account, :small, 48)
end end
def logo(xml, url) def logo(xml, url)

View file

@ -6,7 +6,7 @@ module StreamEntriesHelper
end end
def avatar_for_status_url(status) def avatar_for_status_url(status)
status.reblog? ? status.reblog.account.avatar.url(:large) : status.account.avatar.url(:large) status.reblog? ? status.reblog.account.avatar.expiring_url(3600, :original) : status.account.avatar.expiring_url(3600, :original)
end end
def entry_classes(status, is_predecessor, is_successor, include_threads) def entry_classes(status, is_predecessor, is_successor, include_threads)

View file

@ -13,12 +13,12 @@ class Account < ApplicationRecord
validates :username, presence: true, uniqueness: { scope: :domain, case_sensitive: true }, unless: 'local?' validates :username, presence: true, uniqueness: { scope: :domain, case_sensitive: true }, unless: 'local?'
# Avatar upload # Avatar upload
has_attached_file :avatar, styles: { large: '300x300#' }, convert_options: { all: '-strip' } has_attached_file :avatar, styles: { original: '120x120#' }, convert_options: { all: '-quality 80 -strip' }
validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
validates_attachment_size :avatar, less_than: 2.megabytes validates_attachment_size :avatar, less_than: 2.megabytes
# Header upload # Header upload
has_attached_file :header, styles: { medium: '700x335#' }, convert_options: { all: '-strip' } has_attached_file :header, styles: { original: '700x335#' }, convert_options: { all: '-quality 80 -strip' }
validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
validates_attachment_size :header, less_than: 2.megabytes validates_attachment_size :header, less_than: 2.megabytes

View file

@ -10,7 +10,7 @@ class MediaAttachment < ApplicationRecord
has_attached_file :file, has_attached_file :file,
styles: -> (f) { file_styles f }, styles: -> (f) { file_styles f },
processors: -> (f) { f.video? ? [:transcoder] : [:thumbnail] }, processors: -> (f) { f.video? ? [:transcoder] : [:thumbnail] },
convert_options: { all: '-strip' } convert_options: { all: '-quality 80 -strip' }
validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES
validates_attachment_size :file, less_than: 4.megabytes validates_attachment_size :file, less_than: 4.megabytes

View file

@ -1,6 +1,6 @@
.account-grid-card .account-grid-card
.account-grid-card__header .account-grid-card__header
.avatar= image_tag account.avatar.url(:medium) .avatar= image_tag account.avatar.expiring_url(3600, :original)
.name .name
= link_to TagManager.instance.url_for(account) do = link_to TagManager.instance.url_for(account) do
%span.display_name= display_name(account) %span.display_name= display_name(account)

View file

@ -1,4 +1,4 @@
.card{ style: "background-image: url(#{@account.header.url(:medium)})" } .card{ style: "background-image: url(#{@account.header.expiring_url(3600, :original)})" }
- if user_signed_in? && current_account.id != @account.id - if user_signed_in? && current_account.id != @account.id
.controls .controls
- if current_account.following?(@account) - if current_account.following?(@account)
@ -6,7 +6,7 @@
- else - else
= link_to t('accounts.follow'), follow_account_path(@account), data: { method: :post }, class: 'button' = link_to t('accounts.follow'), follow_account_path(@account), data: { method: :post }, class: 'button'
.avatar= image_tag @account.avatar.url(:large) .avatar= image_tag @account.avatar.expiring_url(3600, :original)
%h1.name %h1.name
= display_name(@account) = display_name(@account)
%small= "@#{@account.username}" %small= "@#{@account.username}"

View file

@ -6,7 +6,7 @@ Nokogiri::XML::Builder.new do |xml|
title xml, @account.display_name title xml, @account.display_name
subtitle xml, @account.note subtitle xml, @account.note
updated_at xml, stream_updated_at updated_at xml, stream_updated_at
logo xml, full_asset_url(@account.avatar.url(:medium, false)) logo xml, full_asset_url(@account.avatar.expiring_url(3600, :original))
author(xml) do author(xml) do
include_author xml, @account include_author xml, @account

View file

@ -4,8 +4,8 @@ attributes :id, :username, :acct, :display_name
node(:note) { |account| Formatter.instance.simplified_format(account) } node(:note) { |account| Formatter.instance.simplified_format(account) }
node(:url) { |account| TagManager.instance.url_for(account) } node(:url) { |account| TagManager.instance.url_for(account) }
node(:avatar) { |account| full_asset_url(account.avatar.url(:large, false)) } node(:avatar) { |account| full_asset_url(account.avatar.expiring_url(3600, :original)) }
node(:header) { |account| full_asset_url(account.header.url(:medium, false)) } node(:header) { |account| full_asset_url(account.header.expiring_url(3600, :original)) }
node(:followers_count) { |account| defined?(@followers_counts_map) ? (@followers_counts_map[account.id] || 0) : (account.try(:followers_count) || account.followers.count) } node(:followers_count) { |account| defined?(@followers_counts_map) ? (@followers_counts_map[account.id] || 0) : (account.try(:followers_count) || account.followers.count) }
node(:following_count) { |account| defined?(@following_counts_map) ? (@following_counts_map[account.id] || 0) : (account.try(:following_count) || account.following.count) } node(:following_count) { |account| defined?(@following_counts_map) ? (@following_counts_map[account.id] || 0) : (account.try(:following_count) || account.following.count) }
node(:statuses_count) { |account| defined?(@statuses_counts_map) ? (@statuses_counts_map[account.id] || 0) : (account.try(:statuses_count) || account.statuses.count) } node(:statuses_count) { |account| defined?(@statuses_counts_map) ? (@statuses_counts_map[account.id] || 0) : (account.try(:statuses_count) || account.statuses.count) }

View file

@ -1,5 +1,5 @@
object @media object @media
attribute :id, :type attribute :id, :type
node(:url) { |media| full_asset_url(media.file.url) } node(:url) { |media| full_asset_url(media.file.expiring_url(3600, :original)) }
node(:preview_url) { |media| full_asset_url(media.file.url(:small)) } node(:preview_url) { |media| full_asset_url(media.file.expiring_url(3600, :small)) }
node(:text_url) { |media| medium_url(media) } node(:text_url) { |media| medium_url(media) }

View file

@ -1,4 +1,4 @@
attributes :id, :remote_url, :type attributes :id, :remote_url, :type
node(:url) { |media| full_asset_url(media.file.url) } node(:url) { |media| full_asset_url(media.file.expiring_url(3600, :original)) }
node(:preview_url) { |media| full_asset_url(media.file.url(:small)) } node(:preview_url) { |media| full_asset_url(media.file.expiring_url(3600, :small)) }

View file

@ -34,7 +34,7 @@
- if (status.reblog? ? status.reblog : status).media_attachments.size > 0 - if (status.reblog? ? status.reblog : status).media_attachments.size > 0
%ul.media-attachments %ul.media-attachments
- (status.reblog? ? status.reblog : status).media_attachments.each do |media| - (status.reblog? ? status.reblog : status).media_attachments.each do |media|
%li.transparent-background= link_to '', media.file.url, style: "background-image: url(#{media.file.url(:small)})", target: '_blank' %li.transparent-background= link_to '', media.file.expiring_url(3600, :original), style: "background-image: url(#{media.file.expiring_url(3600, :small)})", target: '_blank'
- if include_threads - if include_threads
= render partial: 'status', collection: @descendants, as: :status, locals: { is_successor: true } = render partial: 'status', collection: @descendants, as: :status, locals: { is_successor: true }

View file

@ -7,7 +7,7 @@
%meta{ name: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/ %meta{ name: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
%meta{ name: 'og:article:author', content: @account.username }/ %meta{ name: 'og:article:author', content: @account.username }/
%meta{ name: 'og:description', content: @stream_entry.activity.content }/ %meta{ name: 'og:description', content: @stream_entry.activity.content }/
%meta{ name: 'og:image', content: @stream_entry.activity.is_a?(Status) && @stream_entry.activity.media_attachments.size > 0 ? full_asset_url(@stream_entry.activity.media_attachments.first.file.url(:small)) : full_asset_url(@account.avatar.url(:large)) }/ %meta{ name: 'og:image', content: @stream_entry.activity.is_a?(Status) && @stream_entry.activity.media_attachments.size > 0 ? full_asset_url(@stream_entry.activity.media_attachments.first.file.expiring_url(3600, :small)) : full_asset_url(@account.avatar.expiring_url(3600, :original)) }/
.activity-stream.activity-stream-headless .activity-stream.activity-stream-headless
= render partial: @type, locals: { @type.to_sym => @stream_entry.activity, include_threads: true } = render partial: @type, locals: { @type.to_sym => @stream_entry.activity, include_threads: true }

View file

@ -1,11 +1,13 @@
if ENV['S3_ENABLED'] == 'true' if ENV['S3_ENABLED'] == 'true'
Aws.eager_autoload!(services: %w(S3)) Aws.eager_autoload!(services: %w(S3))
Paperclip::Attachment.default_options[:storage] = :s3 Paperclip::Attachment.default_options[:storage] = :s3
Paperclip::Attachment.default_options[:s3_protocol] = 'https' Paperclip::Attachment.default_options[:s3_protocol] = 'https'
Paperclip::Attachment.default_options[:url] = ':s3_domain_url' Paperclip::Attachment.default_options[:url] = ':s3_domain_url'
Paperclip::Attachment.default_options[:s3_host_name] = "s3-#{ENV.fetch('S3_REGION')}.amazonaws.com" Paperclip::Attachment.default_options[:s3_host_name] = "s3-#{ENV.fetch('S3_REGION')}.amazonaws.com"
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename' Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
Paperclip::Attachment.default_options[:s3_headers] = { 'Cache-Control' => 'max-age=315576000', 'Expires' => 10.years.from_now.httpdate }
Paperclip::Attachment.default_options[:s3_permissions] = :private
unless ENV['S3_CLOUDFRONT_HOST'].blank? unless ENV['S3_CLOUDFRONT_HOST'].blank?
Paperclip::Attachment.default_options[:url] = ':s3_alias_url' Paperclip::Attachment.default_options[:url] = ':s3_alias_url'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 B

View file

@ -162,7 +162,7 @@ RSpec.describe AtomBuilderHelper, type: :helper do
let(:account) { Fabricate(:account, username: 'alice') } let(:account) { Fabricate(:account, username: 'alice') }
it 'creates a link' do it 'creates a link' do
expect(used_with_namespaces { |xml| helper.link_avatar(xml, account) }).to match '<link rel="avatar" type="" media:width="300" media:height="300" href="http://test.host/avatars/large/missing.png"/>' expect(used_with_namespaces { |xml| helper.link_avatar(xml, account) }).to match '<link rel="avatar" type="" media:width="120" media:height="120" href="http://test.host/avatars/original/missing.png"/>'
end end
end end