about summary refs log tree commit diff
path: root/app/models/preview_card.rb
blob: a738940be87100d7817f121ead2cd4fcea33c521 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# frozen_string_literal: true

# == Schema Information
#
# Table name: preview_cards
#
#  id                           :bigint(8)        not null, primary key
#  url                          :string           default(""), not null
#  title                        :string           default(""), not null
#  description                  :string           default(""), not null
#  image_file_name              :string
#  image_content_type           :string
#  image_file_size              :integer
#  image_updated_at             :datetime
#  type                         :integer          default("link"), not null
#  html                         :text             default(""), not null
#  author_name                  :string           default(""), not null
#  author_url                   :string           default(""), not null
#  provider_name                :string           default(""), not null
#  provider_url                 :string           default(""), not null
#  width                        :integer          default(0), not null
#  height                       :integer          default(0), not null
#  created_at                   :datetime         not null
#  updated_at                   :datetime         not null
#  embed_url                    :string           default(""), not null
#  image_storage_schema_version :integer
#  blurhash                     :string
#  language                     :string
#  max_score                    :float
#  max_score_at                 :datetime
#  trendable                    :boolean
#  link_type                    :integer
#

class PreviewCard < ApplicationRecord
  include Attachmentable

  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
  LIMIT = 2.megabytes

  BLURHASH_OPTIONS = {
    x_comp: 4,
    y_comp: 4,
  }.freeze

  self.inheritance_column = false

  enum type: { link: 0, photo: 1, video: 2, rich: 3 }
  enum link_type: { unknown: 0, article: 1 }

  has_and_belongs_to_many :statuses
  has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy

  has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set modify-date +set create-date' }, validate_media_type: false

  validates :url, presence: true, uniqueness: true
  validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES
  validates_attachment_size :image, less_than: LIMIT
  remotable_attachment :image, LIMIT

  scope :cached, -> { where.not(image_file_name: [nil, '']) }

  before_save :extract_dimensions, if: :link?

  def appropriate_for_trends?
    link? && article? && title.present? && description.present? && image.present? && provider_name.present?
  end

  def domain
    @domain ||= Addressable::URI.parse(url).normalized_host
  end

  def provider
    @provider ||= PreviewCardProvider.matching_domain(domain)
  end

  def trendable?
    if attributes['trendable'].nil?
      provider&.trendable?
    else
      attributes['trendable']
    end
  end

  def requires_review?
    attributes['trendable'].nil? && (provider.nil? || provider.requires_review?)
  end

  def requires_review_notification?
    attributes['trendable'].nil? && (provider.nil? || provider.requires_review_notification?)
  end

  def decaying?
    max_score_at && max_score_at >= Trends.links.options[:max_score_cooldown].ago && max_score_at < 1.day.ago
  end

  attr_writer :provider

  def local?
    false
  end

  def missing_image?
    width.present? && height.present? && image_file_name.blank?
  end

  def save_with_optional_image!
    save!
  rescue ActiveRecord::RecordInvalid
    self.image = nil
    save!
  end

  def history
    @history ||= Trends::History.new('links', id)
  end

  class << self
    private

    def image_styles(file)
      styles = {
        original: {
          pixels: 230_400, # 640x360px
          file_geometry_parser: FastGeometryParser,
          convert_options: '-coalesce',
          blurhash: BLURHASH_OPTIONS,
        },
      }

      styles[:original][:format] = 'jpg' if file.instance.image_content_type == 'image/gif'
      styles
    end
  end

  private

  def extract_dimensions
    file = image.queued_for_write[:original]

    return if file.nil?

    width, height = FastImage.size(file.path)

    return nil if width.nil?

    self.width  = width
    self.height = height
  end
end